diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index ce956176e..c5f35c088 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -7,6 +7,7 @@ - [anthonylavado](https://github.com/anthonylavado) - [Artiume](https://github.com/Artiume) - [AThomsen](https://github.com/AThomsen) + - [barronpm](https://github.com/barronpm) - [bilde2910](https://github.com/bilde2910) - [bfayers](https://github.com/bfayers) - [BnMcG](https://github.com/BnMcG) @@ -130,6 +131,7 @@ - [XVicarious](https://github.com/XVicarious) - [YouKnowBlom](https://github.com/YouKnowBlom) - [KristupasSavickas](https://github.com/KristupasSavickas) + - [Pusta](https://github.com/pusta) # Emby Contributors diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index 66805b7c8..b1ce7e8ec 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -4,11 +4,12 @@ using System; using System.Linq; using System.Threading.Tasks; using Emby.Dlna.Service; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.TV; @@ -32,7 +33,8 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaEncoder _mediaEncoder; private readonly ITVSeriesManager _tvSeriesManager; - public ContentDirectory(IDlnaManager dlna, + public ContentDirectory( + IDlnaManager dlna, IUserDataManager userDataManager, IImageProcessor imageProcessor, ILibraryManager libraryManager, @@ -131,7 +133,7 @@ namespace Emby.Dlna.ContentDirectory foreach (var user in _userManager.Users) { - if (user.Policy.IsAdministrator) + if (user.HasPermission(PermissionKind.IsAdministrator)) { return user; } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 28888f031..de6b619ba 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Xml; using Emby.Dlna.Didl; using Emby.Dlna.Service; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; @@ -17,7 +18,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; @@ -28,6 +28,12 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Dlna.ContentDirectory { @@ -460,12 +466,12 @@ namespace Emby.Dlna.ContentDirectory } else if (search.SearchType == SearchType.Playlist) { - //items = items.OfType(); + // items = items.OfType(); isFolder = true; } else if (search.SearchType == SearchType.MusicAlbum) { - //items = items.OfType(); + // items = items.OfType(); isFolder = true; } @@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory return GetGenres(item, user, query); } - var array = new ServerItem[] + var array = new[] { new ServerItem(item) { @@ -920,7 +926,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMovieCollections(User user, InternalItemsQuery query) { query.Recursive = true; - //query.Parent = parent; + // query.Parent = parent; query.SetUser(user); query.IncludeItemTypes = new[] { typeof(BoxSet).Name }; @@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query) { query.Parent = null; - query.IncludeItemTypes = new[] { typeof(Playlist).Name }; + query.IncludeItemTypes = new[] { nameof(Playlist) }; query.SetUser(user); query.Recursive = true; @@ -1132,10 +1138,9 @@ namespace Emby.Dlna.ContentDirectory { UserId = user.Id, Limit = 50, - IncludeItemTypes = new[] { typeof(Audio).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, + IncludeItemTypes = new[] { nameof(Audio) }, + ParentId = parent?.Id ?? Guid.Empty, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); @@ -1150,7 +1155,6 @@ namespace Emby.Dlna.ContentDirectory Limit = query.Limit, StartIndex = query.StartIndex, UserId = query.User.Id - }, new[] { parent }, query.DtoOptions); return ToResult(result); @@ -1167,7 +1171,6 @@ namespace Emby.Dlna.ContentDirectory IncludeItemTypes = new[] { typeof(Episode).Name }, ParentId = parent == null ? Guid.Empty : parent.Id, GroupItems = false - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); @@ -1177,14 +1180,14 @@ namespace Emby.Dlna.ContentDirectory { query.OrderBy = Array.Empty<(string, SortOrder)>(); - var items = _userViewManager.GetLatestItems(new LatestItemsQuery + var items = _userViewManager.GetLatestItems( + new LatestItemsQuery { UserId = user.Id, Limit = 50, - IncludeItemTypes = new[] { typeof(Movie).Name }, - ParentId = parent == null ? Guid.Empty : parent.Id, + IncludeItemTypes = new[] { nameof(Movie) }, + ParentId = parent?.Id ?? Guid.Empty, GroupItems = true - }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray(); return ToResult(items); @@ -1217,7 +1220,11 @@ namespace Emby.Dlna.ContentDirectory Recursive = true, ParentId = parentId, GenreIds = new[] { item.Id }, - IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name }, + IncludeItemTypes = new[] + { + nameof(Movie), + nameof(Series) + }, Limit = limit, StartIndex = startIndex, DtoOptions = GetDtoOptions() diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index f7d840c62..6ded76f7d 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -6,14 +6,13 @@ using System.IO; using System.Linq; using System.Text; using System.Xml; -using Emby.Dlna.Configuration; using Emby.Dlna.ContentDirectory; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Playlists; @@ -23,6 +22,13 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; +using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute; namespace Emby.Dlna.Didl { @@ -92,21 +98,21 @@ namespace Emby.Dlna.Didl { using (var writer = XmlWriter.Create(builder, settings)) { - //writer.WriteStartDocument(); + // writer.WriteStartDocument(); writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL); writer.WriteAttributeString("xmlns", "dc", null, NS_DC); writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA); writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP); - //didl.SetAttribute("xmlns:sec", NS_SEC); + // didl.SetAttribute("xmlns:sec", NS_SEC); WriteXmlRootAttributes(_profile, writer); WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo); writer.WriteFullEndElement(); - //writer.WriteEndDocument(); + // writer.WriteEndDocument(); } return builder.ToString(); @@ -421,7 +427,6 @@ namespace Emby.Dlna.Didl case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); case StubType.Series: return _localization.GetLocalizedString("Shows"); - default: break; } } @@ -670,7 +675,7 @@ namespace Emby.Dlna.Didl return; } - MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null; + XmlAttribute secAttribute = null; foreach (var attribute in _profile.XmlRootAttributes) { if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase)) @@ -706,7 +711,7 @@ namespace Emby.Dlna.Didl { // Don't filter on dc:title because not all devices will include it in the filter // MediaMonkey for example won't display content without a title - //if (filter.Contains("dc:title")) + // if (filter.Contains("dc:title")) { AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC); } @@ -745,7 +750,7 @@ namespace Emby.Dlna.Didl AddValue(writer, "dc", "description", desc, NS_DC); } } - //if (filter.Contains("upnp:longDescription")) + // if (filter.Contains("upnp:longDescription")) //{ // if (!string.IsNullOrWhiteSpace(item.Overview)) // { @@ -995,7 +1000,6 @@ namespace Emby.Dlna.Didl } AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN"); - } private void AddImageResElement( @@ -1131,25 +1135,24 @@ namespace Emby.Dlna.Didl if (width == 0 || height == 0) { - //_imageProcessor.GetImageSize(item, imageInfo); + // _imageProcessor.GetImageSize(item, imageInfo); width = null; height = null; } - else if (width == -1 || height == -1) { width = null; height = null; } - //try + // try //{ // var size = _imageProcessor.GetImageSize(imageInfo); // width = size.Width; // height = size.Height; //} - //catch + // catch //{ //} diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs index 412259e90..b730d9db2 100644 --- a/Emby.Dlna/Didl/Filter.cs +++ b/Emby.Dlna/Didl/Filter.cs @@ -12,7 +12,6 @@ namespace Emby.Dlna.Didl public Filter() : this("*") { - } public Filter(string filter) @@ -26,7 +25,7 @@ namespace Emby.Dlna.Didl { // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back. return true; - //return _all || ListHelper.ContainsIgnoreCase(_fields, field); + // return _all || ListHelper.ContainsIgnoreCase(_fields, field); } } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index a85e5c35e..e5f483950 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -88,7 +88,6 @@ namespace Emby.Dlna .Select(i => i.Item2) .ToList(); } - } public DeviceProfile GetDefaultProfile() @@ -251,7 +250,7 @@ namespace Emby.Dlna return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; - //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); + // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase); @@ -566,9 +565,9 @@ namespace Emby.Dlna new Foobar2000Profile(), new SharpSmartTvProfile(), new MediaMonkeyProfile(), - //new Windows81Profile(), - //new WindowsMediaCenterProfile(), - //new WindowsPhoneProfile(), + // new Windows81Profile(), + // new WindowsMediaCenterProfile(), + // new WindowsPhoneProfile(), new DirectTvProfile(), new DishHopperJoeyProfile(), new DefaultProfile(), diff --git a/Emby.Dlna/Eventing/EventManager.cs b/Emby.Dlna/Eventing/EventManager.cs index efbb53b64..5a51569e2 100644 --- a/Emby.Dlna/Eventing/EventManager.cs +++ b/Emby.Dlna/Eventing/EventManager.cs @@ -169,7 +169,6 @@ namespace Emby.Dlna.Eventing { using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false)) { - } } catch (OperationCanceledException) diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 47b235e59..a7b1d384d 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -276,7 +276,7 @@ namespace Emby.Dlna.Main var device = new SsdpRootDevice { - CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info. + CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info. Location = uri, // Must point to the URL that serves your devices UPnP description document. Address = address, SubnetMask = _networkManager.GetLocalIpSubnetMask(address), diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index c7431d143..86b72e264 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -750,7 +750,7 @@ namespace Emby.Dlna.PlayTo if (track == null) { - //If track is null, some vendors do this, use GetMediaInfo instead + // If track is null, some vendors do this, use GetMediaInfo instead return (true, null); } @@ -794,7 +794,6 @@ namespace Emby.Dlna.PlayTo } catch (XmlException) { - } // first try to add a root node with a dlna namesapce @@ -806,7 +805,6 @@ namespace Emby.Dlna.PlayTo } catch (XmlException) { - } // some devices send back invalid xml @@ -816,7 +814,6 @@ namespace Emby.Dlna.PlayTo } catch (XmlException) { - } return null; diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 7403a2a16..f1c69196a 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -22,6 +23,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Photo = MediaBrowser.Controller.Entities.Photo; namespace Emby.Dlna.PlayTo { @@ -446,7 +448,13 @@ namespace Emby.Dlna.PlayTo } } - private PlaylistItem CreatePlaylistItem(BaseItem item, User user, long startPostionTicks, string mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex) + private PlaylistItem CreatePlaylistItem( + BaseItem item, + User user, + long startPostionTicks, + string mediaSourceId, + int? audioStreamIndex, + int? subtitleStreamIndex) { var deviceInfo = _device.Properties; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 9b0339e5d..c0500eb68 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -88,7 +88,7 @@ namespace Emby.Dlna.PlayTo if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 && nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1) { - //_logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); + // _logger.LogDebug("Upnp device {0} does not contain a MediaRenderer device (0).", location); return; } @@ -112,7 +112,6 @@ namespace Emby.Dlna.PlayTo } catch (OperationCanceledException) { - } catch (Exception ex) { @@ -243,7 +242,6 @@ namespace Emby.Dlna.PlayTo } catch { - } _sessionLock.Dispose(); diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 8c1362007..ab262bebf 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -91,7 +91,6 @@ namespace Emby.Dlna.PlayTo using (await _httpClient.SendAsync(options, new HttpMethod("SUBSCRIBE")).ConfigureAwait(false)) { - } } diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index ab5e56ab0..7daac96d1 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -77,7 +77,7 @@ namespace Emby.Dlna.Ssdp // (Optional) Set the filter so we only see notifications for devices we care about // (can be any search target value i.e device type, uuid value etc - any value that appears in the // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method). - //_DeviceLocator.NotificationFilter = "upnp:rootdevice"; + // _DeviceLocator.NotificationFilter = "upnp:rootdevice"; // Connect our event handler so we process devices as they are found _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable; diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 5b0f004d1..8696cb280 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; using MediaBrowser.Controller.Drawing; @@ -14,6 +15,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Photo = MediaBrowser.Controller.Entities.Photo; namespace Emby.Drawing { @@ -349,6 +351,13 @@ namespace Emby.Drawing }); } + /// + public string GetImageCacheTag(User user) + { + return (user.ProfileImage.Path + user.ProfileImage.LastModified.Ticks).GetMD5() + .ToString("N", CultureInfo.InvariantCulture); + } + private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified) { var inputFormat = Path.GetExtension(originalImagePath) diff --git a/Emby.Notifications/Api/NotificationsService.cs b/Emby.Notifications/Api/NotificationsService.cs index 788750796..1ff8a5026 100644 --- a/Emby.Notifications/Api/NotificationsService.cs +++ b/Emby.Notifications/Api/NotificationsService.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Notifications; @@ -149,9 +150,7 @@ namespace Emby.Notifications.Api [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")] public object Get(GetNotificationsSummary request) { - return new NotificationsSummary - { - }; + return new NotificationsSummary(); } public Task Post(AddAdminNotification request) @@ -164,7 +163,10 @@ namespace Emby.Notifications.Api Level = request.Level, Name = request.Name, Url = request.Url, - UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToArray() + UserIds = _userManager.Users + .Where(user => user.HasPermission(PermissionKind.IsAdministrator)) + .Select(user => user.Id) + .ToArray() }; return _notificationManager.SendNotification(notification, CancellationToken.None); diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs index 2792a8f0b..8b281e487 100644 --- a/Emby.Notifications/NotificationManager.cs +++ b/Emby.Notifications/NotificationManager.cs @@ -4,6 +4,8 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -101,7 +103,7 @@ namespace Emby.Notifications switch (request.SendToUserMode.Value) { case SendToUserType.Admins: - return _userManager.Users.Where(i => i.Policy.IsAdministrator) + return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator)) .Select(i => i.Id); case SendToUserType.All: return _userManager.UsersIds; @@ -117,7 +119,7 @@ namespace Emby.Notifications var config = GetConfiguration(); return _userManager.Users - .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i.Policy)) + .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i)) .Select(i => i.Id); } @@ -142,7 +144,7 @@ namespace Emby.Notifications User = user }; - _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Name); + _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username); try { diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 6917efefa..84bec9201 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -88,25 +88,26 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - _userManager.UserCreated += OnUserCreated; - _userManager.UserPasswordChanged += OnUserPasswordChanged; - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserLockedOut += OnUserLockedOut; + _userManager.OnUserCreated += OnUserCreated; + _userManager.OnUserPasswordChanged += OnUserPasswordChanged; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserLockedOut += OnUserLockedOut; return Task.CompletedTask; } - private async void OnUserLockedOut(object sender, GenericEventArgs e) + private async void OnUserLockedOut(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserLockedOutWithName"), - e.Argument.Name), + e.Argument.Username), NotificationType.UserLockedOut.ToString(), - e.Argument.Id)) - .ConfigureAwait(false); + e.Argument.Id) + { + LogSeverity = LogLevel.Error + }).ConfigureAwait(false); } private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) @@ -152,7 +153,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackStoppedNotificationType(item.MediaType), @@ -187,7 +188,7 @@ namespace Emby.Server.Implementations.Activity string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserStartedPlayingItemWithValues"), - user.Name, + user.Username, GetItemName(item), e.DeviceName), GetPlaybackNotificationType(item.MediaType), @@ -304,49 +305,37 @@ namespace Emby.Server.Implementations.Activity }).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) - { - await CreateLogEntry(new ActivityLog( - string.Format( - CultureInfo.InvariantCulture, - _localization.GetLocalizedString("UserPolicyUpdatedWithName"), - e.Argument.Name), - "UserPolicyUpdated", - e.Argument.Id)) - .ConfigureAwait(false); - } - - private async void OnUserDeleted(object sender, GenericEventArgs e) + private async void OnUserDeleted(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDeletedWithName"), - e.Argument.Name), + e.Argument.Username), "UserDeleted", Guid.Empty)) .ConfigureAwait(false); } - private async void OnUserPasswordChanged(object sender, GenericEventArgs e) + private async void OnUserPasswordChanged(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserPasswordChangedWithName"), - e.Argument.Name), + e.Argument.Username), "UserPasswordChanged", e.Argument.Id)) .ConfigureAwait(false); } - private async void OnUserCreated(object sender, GenericEventArgs e) + private async void OnUserCreated(object sender, GenericEventArgs e) { await CreateLogEntry(new ActivityLog( string.Format( CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserCreatedWithName"), - e.Argument.Name), + e.Argument.Username), "UserCreated", e.Argument.Id)) .ConfigureAwait(false); @@ -510,11 +499,10 @@ namespace Emby.Server.Implementations.Activity _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - _userManager.UserCreated -= OnUserCreated; - _userManager.UserPasswordChanged -= OnUserPasswordChanged; - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserLockedOut -= OnUserLockedOut; + _userManager.OnUserCreated -= OnUserCreated; + _userManager.OnUserPasswordChanged -= OnUserPasswordChanged; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserLockedOut -= OnUserLockedOut; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 184c6f399..5772dd479 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -562,11 +562,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required serviceCollection.AddTransient(provider => new Lazy(provider.GetRequiredService)); - serviceCollection.AddSingleton(); // TODO: Refactor to eliminate the circular dependency here so that Lazy isn't required // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation @@ -659,15 +656,11 @@ namespace Emby.Server.Implementations ((SqliteDisplayPreferencesRepository)Resolve()).Initialize(); ((AuthenticationRepository)Resolve()).Initialize(); - ((SqliteUserRepository)Resolve()).Initialize(); SetStaticProperties(); - var userManager = (UserManager)Resolve(); - userManager.Initialize(); - var userDataRepo = (SqliteUserDataRepository)Resolve(); - ((SqliteItemRepository)Resolve()).Initialize(userDataRepo, userManager); + ((SqliteItemRepository)Resolve()).Initialize(userDataRepo, Resolve()); FindParts(); } @@ -750,7 +743,6 @@ namespace Emby.Server.Implementations BaseItem.ProviderManager = Resolve(); BaseItem.LocalizationManager = Resolve(); BaseItem.ItemRepository = Resolve(); - User.UserManager = Resolve(); BaseItem.FileSystem = _fileSystemManager; BaseItem.UserDataManager = Resolve(); BaseItem.ChannelManager = Resolve(); diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs index f9eea66b4..c803d9d82 100644 --- a/Emby.Server.Implementations/Channels/ChannelManager.cs +++ b/Emby.Server.Implementations/Channels/ChannelManager.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; @@ -13,8 +14,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Channels; @@ -24,6 +23,11 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.Channels { @@ -791,7 +795,8 @@ namespace Emby.Server.Implementations.Channels return result; } - private async Task GetChannelItems(IChannel channel, + private async Task GetChannelItems( + IChannel channel, User user, string externalFolderId, ChannelItemSortField? sortField, @@ -1067,7 +1072,7 @@ namespace Emby.Server.Implementations.Channels } // was used for status - //if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) + // if (!string.Equals(item.ExternalEtag ?? string.Empty, info.Etag ?? string.Empty, StringComparison.Ordinal)) //{ // item.ExternalEtag = info.Etag; // forceUpdate = true; diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index cba3975cf..8fb9520d6 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index f816fd54f..53c9ccdbf 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -162,7 +162,6 @@ namespace Emby.Server.Implementations.Data } return false; - }, ReadTransactionMode); } diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs index 6c9bcff0f..3de9d6371 100644 --- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs +++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs @@ -51,7 +51,6 @@ namespace Emby.Server.Implementations.Data _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }); } diff --git a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs index d474f1c6b..63d0321b7 100644 --- a/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs @@ -1,4 +1,4 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 using System; using System.Collections.Generic; diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 43a593f11..d33125661 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -321,7 +321,6 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames); AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames); AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames); - }, TransactionMode); connection.RunQueries(postQueries); @@ -2044,7 +2043,6 @@ namespace Emby.Server.Implementations.Data db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); InsertChapters(idBlob, chapters, db); - }, TransactionMode); } } @@ -4360,7 +4358,7 @@ namespace Emby.Server.Implementations.Data // TODO this seems to be an idea for a better schema where ProviderIds are their own table // buut this is not implemented - //hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); + // hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")"); // TODO this is a really BAD way to do it since the pair: // Tmdb, 1234 matches Tmdb=1234 but also Tmdb=1234567 @@ -4789,7 +4787,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type connection.RunInTransaction(db => { connection.ExecuteAll(sql); - }, TransactionMode); } } @@ -5180,7 +5177,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type } } } - } LogQueryTime("GetItemValueNames", commandText, now); @@ -5631,7 +5627,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from People where ItemId=@ItemId", itemIdBlob); InsertPeople(itemIdBlob, people, db); - }, TransactionMode); } } @@ -5788,7 +5783,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob); InsertMediaStreams(itemIdBlob, streams, db); - }, TransactionMode); } } @@ -6134,7 +6128,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob); InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken); - }, TransactionMode); } } diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs index 6ee6230fc..7e66fa072 100644 --- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -346,7 +347,7 @@ namespace Emby.Server.Implementations.Data var userData = new UserItemData(); userData.Key = reader[0].ToString(); - //userData.UserId = reader[1].ReadGuidFromBlob(); + // userData.UserId = reader[1].ReadGuidFromBlob(); if (reader[2].SQLiteType != SQLiteType.Null) { diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs deleted file mode 100644 index 0c3f26974..000000000 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ /dev/null @@ -1,240 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using MediaBrowser.Common.Json; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly JsonSerializerOptions _jsonOptions; - - public SqliteUserRepository( - ILogger logger, - IServerApplicationPaths appPaths) - : base(logger) - { - _jsonOptions = JsonDefaults.GetOptions(); - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database. - /// - public void Initialize() - { - using (var connection = GetConnection()) - { - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - - RemoveEmptyPasswordHashes(connection); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - private void RemoveEmptyPasswordHashes(ManagedConnection connection) - { - foreach (var user in RetrieveAllUsers(connection)) - { - // If the user password is the sha1 hash of the empty string, remove it - if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) - && !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) - { - continue; - } - - user.Password = null; - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - }, TransactionMode); - } - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToByteArray()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, connection); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = JsonSerializer.SerializeToUtf8Bytes(user, _jsonOptions); - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId")) - { - statement.TryBind("@InternalId", user.InternalId); - statement.TryBind("@data", serialized); - statement.MoveNext(); - } - - }, TransactionMode); - } - } - - private User GetUser(Guid guid, ManagedConnection connection) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - var user = JsonSerializer.Deserialize(row[2].ToBlob(), _jsonOptions); - user.InternalId = id; - user.Id = guid; - return user; - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - using (var connection = GetConnection(true)) - { - return new List(RetrieveAllUsers(connection)); - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - private IEnumerable RetrieveAllUsers(ManagedConnection connection) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - yield return GetUser(row); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (var connection = GetConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } -} diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index e39e0aa78..789cdfc11 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Devices; @@ -16,7 +17,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Users; namespace Emby.Server.Implementations.Devices { @@ -27,11 +27,10 @@ namespace Emby.Server.Implementations.Devices private readonly IServerConfigurationManager _config; private readonly IAuthenticationRepository _authRepo; private readonly Dictionary _capabilitiesCache; + private readonly object _capabilitiesSyncLock = new object(); public event EventHandler>> DeviceOptionsUpdated; - private readonly object _capabilitiesSyncLock = new object(); - public DeviceManager( IAuthenticationRepository authRepo, IJsonSerializer json, @@ -113,7 +112,7 @@ namespace Emby.Server.Implementations.Devices { IEnumerable sessions = _authRepo.Get(new AuthenticationInfoQuery { - //UserId = query.UserId + // UserId = query.UserId HasUser = true }).Items; @@ -175,7 +174,12 @@ namespace Emby.Server.Implementations.Devices throw new ArgumentNullException(nameof(deviceId)); } - if (!CanAccessDevice(user.Policy, deviceId)) + if (user.HasPermission(PermissionKind.EnableAllDevices) || user.HasPermission(PermissionKind.IsAdministrator)) + { + return true; + } + + if (!user.GetPreference(PreferenceKind.EnabledDevices).Contains(deviceId, StringComparer.OrdinalIgnoreCase)) { var capabilities = GetCapabilities(deviceId); @@ -187,20 +191,5 @@ namespace Emby.Server.Implementations.Devices return true; } - - private static bool CanAccessDevice(UserPolicy policy, string id) - { - if (policy.EnableAllDevices) - { - return true; - } - - if (policy.IsAdministrator) - { - return true; - } - - return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase); - } } } diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index f9841082c..41ff7e3ab 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -6,14 +6,14 @@ using System.Globalization; using System.IO; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; @@ -24,6 +24,14 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Person = MediaBrowser.Controller.Entities.Person; +using Photo = MediaBrowser.Controller.Entities.Photo; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.Dto { @@ -384,7 +392,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.ChildCount)) { - dto.ChildCount = dto.ChildCount ?? GetChildCount(folder, user); + dto.ChildCount ??= GetChildCount(folder, user); } } @@ -398,7 +406,6 @@ namespace Emby.Server.Implementations.Dto dto.DateLastMediaAdded = folder.DateLastMediaAdded; } } - else { if (options.EnableUserData) @@ -414,7 +421,7 @@ namespace Emby.Server.Implementations.Dto if (options.ContainsField(ItemFields.BasicSyncInfo)) { - var userCanSync = user != null && user.Policy.EnableContentDownloading; + var userCanSync = user != null && user.HasPermission(PermissionKind.EnableContentDownloading); if (userCanSync && item.SupportsExternalTransfer) { dto.SupportsSync = true; @@ -586,7 +593,6 @@ namespace Emby.Server.Implementations.Dto _logger.LogError(ex, "Error getting person {Name}", c); return null; } - }).Where(i => i != null) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) @@ -939,7 +945,7 @@ namespace Emby.Server.Implementations.Dto dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); } - //if (options.ContainsField(ItemFields.MediaSourceCount)) + // if (options.ContainsField(ItemFields.MediaSourceCount)) //{ // Songs always have one //} @@ -949,13 +955,13 @@ namespace Emby.Server.Implementations.Dto { dto.Artists = hasArtist.Artists; - //var artistItems = _libraryManager.GetArtists(new InternalItemsQuery + // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); - //dto.ArtistItems = artistItems.Items + // dto.ArtistItems = artistItems.Items // .Select(i => // { // var artist = i.Item1; @@ -968,7 +974,7 @@ namespace Emby.Server.Implementations.Dto // .ToList(); // Include artists that are not in the database yet, e.g., just added via metadata editor - //var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); + // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); dto.ArtistItems = hasArtist.Artists //.Except(foundArtists, new DistinctNameComparer()) .Select(i => @@ -993,7 +999,6 @@ namespace Emby.Server.Implementations.Dto } return null; - }).Where(i => i != null).ToArray(); } @@ -1002,13 +1007,13 @@ namespace Emby.Server.Implementations.Dto { dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault(); - //var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery + // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery //{ // EnableTotalRecordCount = false, // ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) } //}); - //dto.AlbumArtists = artistItems.Items + // dto.AlbumArtists = artistItems.Items // .Select(i => // { // var artist = i.Item1; @@ -1044,7 +1049,6 @@ namespace Emby.Server.Implementations.Dto } return null; - }).Where(i => i != null).ToArray(); } @@ -1158,7 +1162,7 @@ namespace Emby.Server.Implementations.Dto // this block will add the series poster for episodes without a poster // TODO maybe remove the if statement entirely - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { episodeSeries = episodeSeries ?? episode.Series; if (episodeSeries != null) @@ -1204,7 +1208,7 @@ namespace Emby.Server.Implementations.Dto // this block will add the series poster for seasons without a poster // TODO maybe remove the if statement entirely - //if (options.ContainsField(ItemFields.SeriesPrimaryImage)) + // if (options.ContainsField(ItemFields.SeriesPrimaryImage)) { series = series ?? season.Series; if (series != null) diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index b9992ae73..c1068522a 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -131,7 +132,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch { - } } } diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index b64439802..632735910 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.EntryPoints private async Task SendMessage(string name, TimerEventInfo info) { - var users = _userManager.Users.Where(i => i.Policy.EnableLiveTvAccess).Select(i => i.Id).ToList(); + var users = _userManager.Users.Where(i => i.HasPermission(PermissionKind.EnableLiveTvAccess)).Select(i => i.Id).ToList(); try { diff --git a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs deleted file mode 100644 index 54f4b67e6..000000000 --- a/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Tasks; - -namespace Emby.Server.Implementations.EntryPoints -{ - /// - /// Class RefreshUsersMetadata. - /// - public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask - { - /// - /// The user manager. - /// - private readonly IUserManager _userManager; - private readonly IFileSystem _fileSystem; - - /// - /// Initializes a new instance of the class. - /// - public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem) - { - _userManager = userManager; - _fileSystem = fileSystem; - } - - /// - public string Name => "Refresh Users"; - - /// - public string Key => "RefreshUsers"; - - /// - public string Description => "Refresh user infos"; - - /// - public string Category => "Library"; - - /// - public bool IsHidden => true; - - /// - public bool IsEnabled => true; - - /// - public bool IsLogged => true; - - /// - public async Task Execute(CancellationToken cancellationToken, IProgress progress) - { - foreach (var user in _userManager.Users) - { - cancellationToken.ThrowIfCancellationRequested(); - - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - public IEnumerable GetDefaultTriggers() - { - return new[] - { - new TaskTriggerInfo - { - IntervalTicks = TimeSpan.FromDays(1).Ticks, - Type = TaskTriggerInfo.TriggerInterval - } - }; - } - } -} diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 11ba6f748..826d4d8dc 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; @@ -68,10 +68,8 @@ namespace Emby.Server.Implementations.EntryPoints /// public Task RunAsync() { - _userManager.UserDeleted += OnUserDeleted; - _userManager.UserUpdated += OnUserUpdated; - _userManager.UserPolicyUpdated += OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated += OnUserConfigurationUpdated; + _userManager.OnUserDeleted += OnUserDeleted; + _userManager.OnUserUpdated += OnUserUpdated; _appHost.HasPendingRestartChanged += OnHasPendingRestartChanged; @@ -153,20 +151,6 @@ namespace Emby.Server.Implementations.EntryPoints await SendMessageToUserSession(e.Argument, "UserDeleted", e.Argument.Id.ToString("N", CultureInfo.InvariantCulture)).ConfigureAwait(false); } - private async void OnUserPolicyUpdated(object sender, GenericEventArgs e) - { - var dto = _userManager.GetUserDto(e.Argument); - - await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false); - } - - private async void OnUserConfigurationUpdated(object sender, GenericEventArgs e) - { - var dto = _userManager.GetUserDto(e.Argument); - - await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false); - } - private async Task SendMessageToAdminSessions(string name, T data) { try @@ -175,7 +159,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception) { - } } @@ -191,7 +174,6 @@ namespace Emby.Server.Implementations.EntryPoints } catch (Exception) { - } } @@ -210,10 +192,8 @@ namespace Emby.Server.Implementations.EntryPoints { if (dispose) { - _userManager.UserDeleted -= OnUserDeleted; - _userManager.UserUpdated -= OnUserUpdated; - _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; - _userManager.UserConfigurationUpdated -= OnUserConfigurationUpdated; + _userManager.OnUserDeleted -= OnUserDeleted; + _userManager.OnUserUpdated -= OnUserUpdated; _installationManager.PluginUninstalled -= OnPluginUninstalled; _installationManager.PackageInstalling -= OnPackageInstalling; diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index ecdce89ce..b207397bd 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -43,7 +43,6 @@ namespace Emby.Server.Implementations.EntryPoints _logger = logger; _appHost = appHost; _config = configuration; - } /// diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index cc4797790..d254d394f 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -580,7 +580,6 @@ namespace Emby.Server.Implementations.HttpServer } catch (NotSupportedException) { - } } diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index 85c3db9b2..3ab5dbc16 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.Text; using MediaBrowser.Controller.Net; diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 1b68617c1..72959003a 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -3,10 +3,11 @@ using System; using System.Linq; using Emby.Server.Implementations.SocketSharp; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; @@ -90,7 +91,8 @@ namespace Emby.Server.Implementations.HttpServer.Security !string.IsNullOrEmpty(auth.Client) && !string.IsNullOrEmpty(auth.Device)) { - _sessionManager.LogSessionActivity(auth.Client, + _sessionManager.LogSessionActivity( + auth.Client, auth.Version, auth.DeviceId, auth.Device, @@ -104,21 +106,21 @@ namespace Emby.Server.Implementations.HttpServer.Security private void ValidateUserAccess( User user, IRequest request, - IAuthenticationAttributes authAttribtues, + IAuthenticationAttributes authAttributes, AuthorizationInfo auth) { - if (user.Policy.IsDisabled) + if (user.HasPermission(PermissionKind.IsDisabled)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.IsAdministrator - && !authAttribtues.EscapeParentalControl + if (!user.HasPermission(PermissionKind.IsAdministrator) + && !authAttributes.EscapeParentalControl && !user.IsParentalScheduleAllowed()) { request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl"); @@ -180,7 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security { if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.IsAdministrator) + if (user == null || !user.HasPermission(PermissionKind.IsAdministrator)) { throw new SecurityException("User does not have admin access."); } @@ -188,7 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDeletion) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion)) { throw new SecurityException("User does not have delete access."); } @@ -196,7 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) { - if (user == null || !user.Policy.EnableContentDownloading) + if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading)) { throw new SecurityException("User does not have download access."); } @@ -223,7 +225,7 @@ namespace Emby.Server.Implementations.HttpServer.Security throw new AuthenticationException("Access token is invalid or expired."); } - //if (!string.IsNullOrEmpty(info.UserId)) + // if (!string.IsNullOrEmpty(info.UserId)) //{ // var user = _userManager.GetUserById(info.UserId); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 129faeaab..4dffcd92d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -116,7 +116,6 @@ namespace Emby.Server.Implementations.HttpServer.Security { info.Device = tokenInfo.DeviceName; } - else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) @@ -149,9 +148,9 @@ namespace Emby.Server.Implementations.HttpServer.Security { info.User = _userManager.GetUserById(tokenInfo.UserId); - if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) + if (info.User != null && !string.Equals(info.User.Username, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) { - tokenInfo.UserName = info.User.Name; + tokenInfo.UserName = info.User.Username; updateToken = true; } } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 166952c64..03fcfa53d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,7 +1,7 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index c64d57339..316cd84cf 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -234,10 +234,12 @@ namespace Emby.Server.Implementations.HttpServer private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; - return SendAsync(new WebSocketMessage - { - MessageType = "KeepAlive" - }, CancellationToken.None); + return SendAsync( + new WebSocketMessage + { + MessageId = Guid.NewGuid(), + MessageType = "KeepAlive" + }, CancellationToken.None); } /// diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 49bca7dac..a32b03aaa 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -266,7 +266,6 @@ namespace Emby.Server.Implementations.IO { DisposeWatcher(newWatcher, false); } - } catch (Exception ex) { @@ -393,7 +392,6 @@ namespace Emby.Server.Implementations.IO } return false; - })) { monitorPath = false; diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 2bcfc82b6..a7bbf6acc 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.IO { result.IsDirectory = info is DirectoryInfo || (info.Attributes & FileAttributes.Directory) == FileAttributes.Directory; - //if (!result.IsDirectory) + // if (!result.IsDirectory) //{ // result.IsHidden = (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; //} diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs index dc8062b45..da88b8d8a 100644 --- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs @@ -71,7 +71,6 @@ namespace Emby.Server.Implementations.Images new ValueTuple(ItemSortBy.Random, SortOrder.Ascending) }, IncludeItemTypes = includeItemTypes - }); } diff --git a/Emby.Server.Implementations/Images/DynamicImageProvider.cs b/Emby.Server.Implementations/Images/DynamicImageProvider.cs index ca0aa4a9f..462eb03a8 100644 --- a/Emby.Server.Implementations/Images/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/DynamicImageProvider.cs @@ -78,7 +78,6 @@ namespace Emby.Server.Implementations.Images } return i; - }).GroupBy(x => x.Id) .Select(x => x.First()); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index a57219143..1d4651da2 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -17,6 +17,8 @@ using Emby.Server.Implementations.Library.Resolvers; using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.ScheduledTasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -25,7 +27,6 @@ using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -46,6 +47,9 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Tasks; using MediaBrowser.Providers.MediaInfo; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; using SortOrder = MediaBrowser.Model.Entities.SortOrder; using VideoResolver = Emby.Naming.Video.VideoResolver; @@ -1539,7 +1543,8 @@ namespace Emby.Server.Implementations.Library } // Handle grouping - if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) && user.Configuration.GroupedFolders.Length > 0) + if (user != null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType) + && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) { return GetUserRootFolder() .GetChildren(user, true) @@ -2590,7 +2595,7 @@ namespace Emby.Server.Implementations.Library Anime series don't generally have a season in their file name, however, tvdb needs a season to correctly get the metadata. Hence, a null season needs to be filled with something. */ - //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified + // FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified episode.ParentIndexNumber = 1; } @@ -2856,7 +2861,6 @@ namespace Emby.Server.Implementations.Library _logger.LogError(ex, "Error getting person"); return null; } - }).Where(i => i != null).ToList(); } @@ -2986,7 +2990,7 @@ namespace Emby.Server.Implementations.Library private static bool ValidateNetworkPath(string path) { - //if (Environment.OSVersion.Platform == PlatformID.Win32NT) + // if (Environment.OSVersion.Platform == PlatformID.Win32NT) //{ // // We can't validate protocol-based paths, so just allow them // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1) diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs index ed7d8aa40..9b9f53049 100644 --- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs +++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs @@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library { mediaInfo = _json.DeserializeFromFile(cacheFilePath); - //_logger.LogDebug("Found cached media info"); + // _logger.LogDebug("Found cached media info"); } catch { @@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Library Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _json.SerializeToFile(mediaInfo, cacheFilePath); - //_logger.LogDebug("Saved media info to {0}", cacheFilePath); + // _logger.LogDebug("Saved media info to {0}", cacheFilePath); } } @@ -148,17 +148,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index 00826c7cb..919261027 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -7,6 +7,8 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Entities; @@ -14,7 +16,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -190,10 +191,7 @@ namespace Emby.Server.Implementations.Library { if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)) { - if (!user.Policy.EnableAudioPlaybackTranscoding) - { - source.SupportsTranscoding = false; - } + source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding); } } } @@ -352,7 +350,9 @@ namespace Emby.Server.Implementations.Library private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { - if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) + if (userData.SubtitleStreamIndex.HasValue + && user.RememberSubtitleSelections + && user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) { var index = userData.SubtitleStreamIndex.Value; // Make sure the saved index is still valid @@ -363,26 +363,27 @@ namespace Emby.Server.Implementations.Library } } - var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference) - ? Array.Empty() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference); + + var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference) + ? Array.Empty() : NormalizeLanguage(user.SubtitleLanguagePreference); var defaultAudioIndex = source.DefaultAudioStreamIndex; var audioLangage = defaultAudioIndex == null ? null : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); - source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, + source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex( + source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, + user.SubtitleMode, audioLangage); - MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, - user.Configuration.SubtitleMode, audioLangage); + MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); } private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) { - if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection) + if (userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) { var index = userData.AudioStreamIndex.Value; // Make sure the saved index is still valid @@ -393,11 +394,11 @@ namespace Emby.Server.Implementations.Library } } - var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) + var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference) ? Array.Empty() - : NormalizeLanguage(user.Configuration.AudioLanguagePreference); + : NormalizeLanguage(user.AudioLanguagePreference); - source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack); + source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); } public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) @@ -435,7 +436,6 @@ namespace Emby.Server.Implementations.Library } return 1; - }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) .ThenByDescending(i => { @@ -534,7 +534,7 @@ namespace Emby.Server.Implementations.Library mediaSource.RunTimeTicks = null; } - var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio); + var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); if (audioStream == null || audioStream.Index == -1) { @@ -545,7 +545,7 @@ namespace Emby.Server.Implementations.Library mediaSource.DefaultAudioStreamIndex = audioStream.Index; } - var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video); + var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); if (videoStream != null) { if (!videoStream.BitRate.HasValue) @@ -556,17 +556,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; @@ -622,7 +619,6 @@ namespace Emby.Server.Implementations.Library MediaSource = mediaSource, ExtractChapters = false, MediaType = DlnaProfileType.Video - }, cancellationToken).ConfigureAwait(false); mediaSource.MediaStreams = info.MediaStreams; @@ -648,7 +644,7 @@ namespace Emby.Server.Implementations.Library { mediaInfo = _jsonSerializer.DeserializeFromFile(cacheFilePath); - //_logger.LogDebug("Found cached media info"); + // _logger.LogDebug("Found cached media info"); } catch (Exception ex) { @@ -670,20 +666,21 @@ namespace Emby.Server.Implementations.Library mediaSource.AnalyzeDurationMs = 3000; } - mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest + mediaInfo = await _mediaEncoder.GetMediaInfo( + new MediaInfoRequest { MediaSource = mediaSource, MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video, ExtractChapters = false - - }, cancellationToken).ConfigureAwait(false); + }, + cancellationToken).ConfigureAwait(false); if (cacheFilePath != null) { Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)); _jsonSerializer.SerializeToFile(mediaInfo, cacheFilePath); - //_logger.LogDebug("Saved media info to {0}", cacheFilePath); + // _logger.LogDebug("Saved media info to {0}", cacheFilePath); } } @@ -749,17 +746,14 @@ namespace Emby.Server.Implementations.Library { videoStream.BitRate = 30000000; } - else if (width >= 1900) { videoStream.BitRate = 20000000; } - else if (width >= 1200) { videoStream.BitRate = 8000000; } - else if (width >= 700) { videoStream.BitRate = 2000000; diff --git a/Emby.Server.Implementations/Library/MediaStreamSelector.cs b/Emby.Server.Implementations/Library/MediaStreamSelector.cs index e27145a1d..ca904c4ec 100644 --- a/Emby.Server.Implementations/Library/MediaStreamSelector.cs +++ b/Emby.Server.Implementations/Library/MediaStreamSelector.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; -using MediaBrowser.Model.Configuration; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; namespace Emby.Server.Implementations.Library diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs index 1ec578371..0bdc59914 100644 --- a/Emby.Server.Implementations/Library/MusicManager.cs +++ b/Emby.Server.Implementations/Library/MusicManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Library { @@ -75,7 +77,6 @@ namespace Emby.Server.Implementations.Library { return Guid.Empty; } - }).Where(i => !i.Equals(Guid.Empty)).ToArray(); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); @@ -105,32 +106,27 @@ namespace Emby.Server.Implementations.Library return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions); } - var playlist = item as Playlist; - if (playlist != null) + if (item is Playlist playlist) { return GetInstantMixFromPlaylist(playlist, user, dtoOptions); } - var album = item as MusicAlbum; - if (album != null) + if (item is MusicAlbum album) { return GetInstantMixFromAlbum(album, user, dtoOptions); } - var artist = item as MusicArtist; - if (artist != null) + if (item is MusicArtist artist) { return GetInstantMixFromArtist(artist, user, dtoOptions); } - var song = item as Audio; - if (song != null) + if (item is Audio song) { return GetInstantMixFromSong(song, user, dtoOptions); } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { return GetInstantMixFromFolder(folder, user, dtoOptions); } diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs index fefc8e789..03059e6d3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/AudioResolver.cs @@ -209,8 +209,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio Name = parseName ? resolvedItem.Name : Path.GetFileNameWithoutExtension(firstMedia.Path), - //AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(), - //LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray() + // AdditionalParts = resolvedItem.Files.Skip(1).Select(i => i.Path).ToArray(), + // LocalAlternateVersions = resolvedItem.AlternateVersions.Select(i => i.Path).ToArray() }; result.Items.Add(libraryItem); diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index ebfe95d0a..79b6dded3 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio // Args points to an album if parent is an Artist folder or it directly contains music if (args.IsDirectory) { - // if (args.Parent is MusicArtist) return true; //saves us from testing children twice + // if (args.Parent is MusicArtist) return true; // saves us from testing children twice if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, _libraryManager)) { return true; diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs index fb75593bd..2f5e46038 100644 --- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs @@ -292,7 +292,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } return true; - //var blurayExtensions = new[] + // var blurayExtensions = new[] //{ // ".mts", // ".m2ts", @@ -300,7 +300,7 @@ namespace Emby.Server.Implementations.Library.Resolvers // ".mpls" //}; - //return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)); + // return directoryService.GetFiles(fullPath).Any(i => blurayExtensions.Contains(i.Extension ?? string.Empty, StringComparer.OrdinalIgnoreCase)); } } } diff --git a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs index 1030ed39d..4819f2fc0 100644 --- a/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs @@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.Library.Resolvers } if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase)) { - return new UserRootFolder(); //if we got here and still a root - must be user root + return new UserRootFolder(); // if we got here and still a root - must be user root } if (args.IsVf) { @@ -73,7 +73,6 @@ namespace Emby.Server.Implementations.Library.Resolvers { return false; } - }) .Select(i => _fileSystem.GetFileNameWithoutExtension(i)) .FirstOrDefault(); diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs index 7f8800a64..c43a0ec6b 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs @@ -94,7 +94,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV _localization.GetLocalizedString("NameSeasonNumber"), seasonNumber, args.GetLibraryOptions().PreferredMetadataLanguage); - } return season; diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs index 2f206f74c..732bfd94d 100644 --- a/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs @@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV var collectionType = args.GetCollectionType(); if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - //if (args.ContainsFileSystemEntryByName("tvshow.nfo")) + // if (args.ContainsFileSystemEntryByName("tvshow.nfo")) //{ // return new Series // { diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index bde77ee8e..b8c42cdf8 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -12,6 +13,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Search; using Microsoft.Extensions.Logging; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { @@ -204,7 +207,6 @@ namespace Emby.Server.Implementations.Library return mediaItems.Select(i => new SearchHintInfo { Item = i - }).ToList(); } } diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs index a463d37f0..9e17fa672 100644 --- a/Emby.Server.Implementations/Library/UserDataManager.cs +++ b/Emby.Server.Implementations/Library/UserDataManager.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; +using Book = MediaBrowser.Controller.Entities.Book; namespace Emby.Server.Implementations.Library { diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs deleted file mode 100644 index 531e00666..000000000 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ /dev/null @@ -1,1107 +0,0 @@ -#pragma warning disable CS1591 - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Cryptography; -using MediaBrowser.Common.Events; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// - /// Class UserManager. - /// - public class UserManager : IUserManager - { - private readonly object _policySyncLock = new object(); - private readonly object _configSyncLock = new object(); - - private readonly ILogger _logger; - private readonly IUserRepository _userRepository; - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - private readonly INetworkManager _networkManager; - private readonly IImageProcessor _imageProcessor; - private readonly Lazy _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptoProvider; - - private ConcurrentDictionary _users; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - private InvalidAuthProvider _invalidAuthProvider; - - private IPasswordResetProvider[] _passwordResetProviders; - private DefaultPasswordResetProvider _defaultPasswordResetProvider; - - private IDtoService DtoService => _dtoServiceFactory.Value; - - public UserManager( - ILogger logger, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - IImageProcessor imageProcessor, - Lazy dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptoProvider) - { - _logger = logger; - _userRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessor = imageProcessor; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - _cryptoProvider = cryptoProvider; - _users = null; - } - - public event EventHandler> UserPasswordChanged; - - /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - - public event EventHandler> UserPolicyUpdated; - - public event EventHandler> UserConfigurationUpdated; - - public event EventHandler> UserLockedOut; - - public event EventHandler> UserCreated; - - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - - /// - public IEnumerable Users => _users.Values; - - /// - public IEnumerable UsersIds => _users.Keys; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs(user)); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public NameIdPair[] GetPasswordResetProviders() - { - return _passwordResetProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetPasswordResetProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - - _invalidAuthProvider = _authenticationProviders.OfType().First(); - - _passwordResetProviders = passwordResetProviders.ToArray(); - - _defaultPasswordResetProvider = passwordResetProviders.OfType().First(); - } - - /// - public User GetUserById(Guid id) - { - if (id == Guid.Empty) - { - throw new ArgumentException("Guid can't be empty", nameof(id)); - } - - _users.TryGetValue(id, out User user); - return user; - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentException("Invalid username", nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - LoadUsers(); - - var users = Users; - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - - public static bool IsValidUsername(string username) - { - // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ - // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness - // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, @"^[\w\-'._@]*$"); - } - - private static bool IsValidUsernameCharacter(char i) - => IsValidUsername(i.ToString(CultureInfo.InvariantCulture)); - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - - return builder.ToString(); - } - - public async Task AuthenticateUser( - string username, - string password, - string hashedPassword, - string remoteEndPoint, - bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); - throw new ArgumentNullException(nameof(username)); - } - - var user = Users.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - success = authResult.success; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.authenticationProvider; - string updatedUsername = authResult.username; - success = authResult.success; - - if (success - && authenticationProvider != null - && !(authenticationProvider is DefaultAuthenticationProvider)) - { - // Trust the username returned by the authentication provider - username = updatedUsername; - - // Search the database for the user again - // the authentication provider might have created it - user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint); - throw new AuthenticationException("Invalid username or password entered."); - } - - if (user.Policy.IsDisabled) - { - _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator."); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint); - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - - ResetInvalidLoginAttemptCount(user); - _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name); - } - else - { - IncrementInvalidLoginAttemptCount(user); - _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint); - } - - return success ? user : null; - } - -#nullable enable - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private static string GetPasswordResetProviderId(IPasswordResetProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user)[0]; - } - - private IPasswordResetProvider GetPasswordResetProvider(User user) - { - return GetPasswordResetProviders(user)[0]; - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User? user) - { - var authenticationProviderId = user?.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found - _logger.LogWarning("User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", user?.Name, user?.Policy.AuthenticationProviderId); - providers = new IAuthenticationProvider[] { _invalidAuthProvider }; - } - - return providers; - } - - private IPasswordResetProvider[] GetPasswordResetProviders(User? user) - { - var passwordResetProviderId = user?.Policy.PasswordResetProviderId; - - var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(passwordResetProviderId)) - { - providers = providers.Where(i => string.Equals(passwordResetProviderId, GetPasswordResetProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IPasswordResetProvider[] { _defaultPasswordResetProvider }; - } - - return providers; - } - - private async Task<(string username, bool success)> AuthenticateWithProvider( - IAuthenticationProvider provider, - string username, - string password, - User? resolvedUser) - { - try - { - var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser - ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) - : await provider.Authenticate(username, password).ConfigureAwait(false); - - if (authenticationResult.Username != username) - { - _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); - username = authenticationResult.Username; - } - - return (username, true); - } - catch (AuthenticationException ex) - { - _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); - - return (username, false); - } - } - - private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser( - string username, - string password, - string hashedPassword, - User? user, - string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider? authenticationProvider = null; - - foreach (var provider in GetAuthenticationProviders(user)) - { - var providerAuthResult = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - var updatedUsername = providerAuthResult.username; - success = providerAuthResult.success; - - if (success) - { - authenticationProvider = provider; - username = updatedUsername; - break; - } - } - - if (!success - && _networkManager.IsInLocalNetwork(remoteEndPoint) - && user?.Configuration.EnableLocalPassword == true - && !string.IsNullOrEmpty(user.EasyPassword)) - { - // Check easy password - var passwordHash = PasswordHash.Parse(user.EasyPassword); - var hash = _cryptoProvider.ComputeHash( - passwordHash.Id, - Encoding.UTF8.GetBytes(password), - passwordHash.Salt.ToArray()); - success = passwordHash.Hash.SequenceEqual(hash); - } - - return (authenticationProvider, username, success); - } - - private void ResetInvalidLoginAttemptCount(User user) - { - user.Policy.InvalidLoginAttemptCount = 0; - UpdateUserPolicy(user, user.Policy, false); - } - - private void IncrementInvalidLoginAttemptCount(User user) - { - int invalidLogins = ++user.Policy.InvalidLoginAttemptCount; - int maxInvalidLogins = user.Policy.LoginAttemptsBeforeLockout; - if (maxInvalidLogins > 0 - && invalidLogins >= maxInvalidLogins) - { - user.Policy.IsDisabled = true; - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - _logger.LogWarning( - "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.", - user.Name, - invalidLogins); - } - - UpdateUserPolicy(user, user.Policy, false); - } - - /// - /// Loads the users from the repository. - /// - private void LoadUsers() - { - var users = _userRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count != 0) - { - _users = new ConcurrentDictionary( - users.Select(x => new KeyValuePair(x.Id, x))); - return; - } - - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - - _logger.LogWarning("No users, creating one with username {UserName}", defaultName); - - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.CreateUser(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - - _users = new ConcurrentDictionary(); - _users[user.Id] = user; - } - -#nullable restore - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user); - bool hasConfiguredEasyPassword = !string.IsNullOrEmpty(GetAuthenticationProvider(user).GetEasyPasswordHash(user)); - - bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - UserDto dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && _users.Count == 1) - { - dto.EnableAutoLogin = true; - } - - ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - DtoService.AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {User}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessor.GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {ImageType} image info for {ImagePath}", image.Type, image.Path); - return null; - } - } - - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Renames the user. - /// - /// The user. - /// The new name. - /// Task. - /// user - /// - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrWhiteSpace(newName)) - { - throw new ArgumentException("Invalid username", nameof(newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - if (Users.Any( - u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "A user with the name '{0}' already exists.", - newName)); - } - - await user.Rename(newName).ConfigureAwait(false); - - OnUserUpdated(user); - } - - /// - /// Updates the user. - /// - /// The user. - /// user - /// - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id == Guid.Empty) - { - throw new ArgumentException("Id can't be empty.", nameof(user)); - } - - if (!_users.ContainsKey(user.Id)) - { - throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - "A user '{0}' with Id {1} does not exist.", - user.Name, - user.Id), - nameof(user)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - /// - /// Creates the user. - /// - /// The name. - /// User. - /// name - /// - public User CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); - } - - var user = InstantiateNewUser(name); - - _users[user.Id] = user; - - user.DateLastSaved = DateTime.UtcNow; - - _userRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs(user), _logger); - - return user; - } - - /// - /// The user is null. - /// The user doesn't exist, or is the last administrator. - /// The user can't be deleted; there are no other users. - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (!_users.ContainsKey(user.Id)) - { - throw new ArgumentException(string.Format( - CultureInfo.InvariantCulture, - "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", - user.Name, - user.Id)); - } - - if (_users.Count == 1) - { - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one user in the system.", - user.Name)); - } - - if (user.Policy.IsAdministrator - && Users.Count(i => i.Policy.IsAdministrator) == 1) - { - throw new ArgumentException( - string.Format( - CultureInfo.InvariantCulture, - "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", - user.Name), - nameof(user)); - } - - var configPath = GetConfigurationFilePath(user); - - _userRepository.DeleteUser(user); - - // Delete user config dir - lock (_configSyncLock) - lock (_policySyncLock) - { - try - { - Directory.Delete(user.ConfigurationDirectoryPath, true); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting user config dir: {Path}", user.ConfigurationDirectoryPath); - } - } - - _users.TryRemove(user.Id, out _); - - OnUserDeleted(user); - } - - /// - /// Resets the password by clearing it. - /// - /// Task. - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordHash); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Instantiates the new user. - /// - /// The name. - /// User. - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow - }; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - - if (user != null && isInNetwork) - { - var passwordResetProvider = GetPasswordResetProvider(user); - return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); - } - else - { - return new ForgotPasswordResult - { - Action = action, - PinFile = string.Empty - }; - } - } - - public async Task RedeemPasswordResetPin(string pin) - { - foreach (var provider in _passwordResetProviders) - { - var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); - if (result.Success) - { - return result; - } - } - - return new PinRedeemResult - { - Success = false, - UsersReset = Array.Empty() - }; - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - if (!File.Exists(path)) - { - return GetDefaultPolicy(); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {Path}", path); - - return GetDefaultPolicy(); - } - } - - private static UserPolicy GetDefaultPolicy() - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs(user)); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {Path}", path); - - return new UserConfiguration(); - } - } - - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs(user)); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 322819b05..f51657c63 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; @@ -17,6 +19,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace Emby.Server.Implementations.Library { @@ -125,12 +129,12 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeHidden) { - list = list.Where(i => !user.Configuration.MyMediaExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); + list = list.Where(i => !user.GetPreference(PreferenceKind.MyMediaExcludes).Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))).ToList(); } var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); - var orders = user.Configuration.OrderedViews.ToList(); + var orders = user.GetPreference(PreferenceKind.OrderedViews).ToList(); return list .OrderBy(i => @@ -165,7 +169,13 @@ namespace Emby.Server.Implementations.Library return GetUserSubViewWithName(name, parentId, type, sortName); } - private Folder GetUserView(List parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews) + private Folder GetUserView( + List parents, + string viewType, + string localizationKey, + string sortName, + Jellyfin.Data.Entities.User user, + string[] presetViews) { if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase))) { @@ -270,7 +280,8 @@ namespace Emby.Server.Implementations.Library { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToList(); } @@ -331,12 +342,11 @@ namespace Emby.Server.Implementations.Library var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Count == 0 ? new[] { - typeof(Person).Name, - typeof(Studio).Name, - typeof(Year).Name, - typeof(MusicGenre).Name, - typeof(Genre).Name - + nameof(Person), + nameof(Studio), + nameof(Year), + nameof(MusicGenre), + nameof(Genre) } : Array.Empty(); var query = new InternalItemsQuery(user) diff --git a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs index 8a6bd5e78..d4c8c35e6 100644 --- a/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Library.Validators _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }, false); } diff --git a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs index 7a6cd11df..ca35adfff 100644 --- a/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -92,7 +92,6 @@ namespace Emby.Server.Implementations.Library.Validators _libraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false - }, false); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 9fb5aec29..7b0fcbc9e 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1547,7 +1547,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV IsFolder = false, Recursive = true, DtoOptions = new DtoOptions(true) - }) .Where(i => i.IsFileProtocol && File.Exists(i.Path)) .Skip(seriesTimer.KeepUpTo - 1) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 70dd8f321..d8ec107ec 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -183,7 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn"; - //var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? + // var outputParam = string.Equals(Path.GetExtension(targetFile), ".mp4", StringComparison.OrdinalIgnoreCase) ? // " -f mp4 -movflags frag_keyframe+empty_moov" : // string.Empty; @@ -206,13 +206,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { return "-codec:a:0 copy"; - //var audioChannels = 2; - //var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); - //if (audioStream != null) + // var audioChannels = 2; + // var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio); + // if (audioStream != null) //{ // audioChannels = audioStream.Channels ?? audioChannels; //} - //return "-codec:a:0 aac -strict experimental -ab 320000"; + // return "-codec:a:0 aac -strict experimental -ab 320000"; } private static bool EncodeVideo(MediaSourceInfo mediaSource) diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index 0b0ff6cb3..142c59542 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -56,7 +56,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV name += " " + info.EpisodeTitle; } } - else if (info.IsMovie && info.ProductionYear != null) { name += " (" + info.ProductionYear + ")"; diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index a65b3ee03..fdd305f86 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -145,7 +145,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings var programsInfo = new List(); foreach (ScheduleDirect.Program schedule in dailySchedules.SelectMany(d => d.programs)) { - //_logger.LogDebug("Proccesing Schedule for statio ID " + stationID + + // _logger.LogDebug("Proccesing Schedule for statio ID " + stationID + // " which corresponds to channel " + channelNumber + " and program id " + // schedule.programID + " which says it has images? " + // programDict[schedule.programID].hasImageArtwork); @@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings programEntry.backdropImage = GetProgramImage(ApiUrl, imagesWithoutText, true, WideAspect); - //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? + // programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ?? // GetProgramImage(ApiUrl, data, "Banner-L1", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LO", false) ?? // GetProgramImage(ApiUrl, data, "Banner-LOT", false); @@ -276,7 +276,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings CommunityRating = null, EpisodeTitle = episodeTitle, Audio = audioType, - //IsNew = programInfo.@new ?? false, + // IsNew = programInfo.@new ?? false, IsRepeat = programInfo.@new == null, IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase), ImageUrl = details.primaryImage, @@ -701,7 +701,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings CancellationToken = cancellationToken, LogErrorResponseBody = true }; - //_logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + + // _logger.LogInformation("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " + // httpOptions.RequestContent); using (var response = await Post(httpOptions, false, null).ConfigureAwait(false)) @@ -1218,7 +1218,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings public string programID { get; set; } public List data { get; set; } } - } } } diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index a88e0f31f..42e93b7ff 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -14,8 +16,6 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Persistence; @@ -31,6 +31,8 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace Emby.Server.Implementations.LiveTv { @@ -404,8 +406,8 @@ namespace Emby.Server.Implementations.LiveTv if (!(service is EmbyTV.EmbyTV)) { // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says - //mediaSource.SupportsDirectPlay = false; - //mediaSource.SupportsDirectStream = false; + // mediaSource.SupportsDirectPlay = false; + // mediaSource.SupportsDirectStream = false; mediaSource.SupportsTranscoding = true; foreach (var stream in mediaSource.MediaStreams) { @@ -556,7 +558,7 @@ namespace Emby.Server.Implementations.LiveTv } item.ParentId = channel.Id; - //item.ChannelType = channelType; + // item.ChannelType = channelType; item.Audio = info.Audio; item.ChannelId = channel.Id; @@ -696,7 +698,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.ThumbImageUrl, Type = ImageType.Thumb - }, 0); } } @@ -709,7 +710,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.LogoImageUrl, Type = ImageType.Logo - }, 0); } } @@ -722,7 +722,6 @@ namespace Emby.Server.Implementations.LiveTv { Path = info.BackdropImageUrl, Type = ImageType.Backdrop - }, 0); } } @@ -760,7 +759,8 @@ namespace Emby.Server.Implementations.LiveTv var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user); - var list = new List>() { + var list = new List> + { new Tuple(dto, program.ExternalId, program.ExternalSeriesId) }; @@ -1168,7 +1168,6 @@ namespace Emby.Server.Implementations.LiveTv IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name }, ChannelIds = new Guid[] { currentChannel.Id }, DtoOptions = new DtoOptions(true) - }).Cast().ToDictionary(i => i.Id); var newPrograms = new List(); @@ -1368,10 +1367,10 @@ namespace Emby.Server.Implementations.LiveTv // limit = (query.Limit ?? 10) * 2; limit = null; - //var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray(); - //var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray(); + // var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray(); + // var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray(); - //return new QueryResult + // return new QueryResult //{ // Items = items, // TotalRecordCount = items.Length @@ -1738,7 +1737,6 @@ namespace Emby.Server.Implementations.LiveTv var results = await GetTimers(new TimerQuery { Id = id - }, cancellationToken).ConfigureAwait(false); return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)); @@ -1790,7 +1788,6 @@ namespace Emby.Server.Implementations.LiveTv .Select(i => { return i.Item1; - }) .ToArray(); @@ -1845,7 +1842,6 @@ namespace Emby.Server.Implementations.LiveTv } return _tvDtoService.GetSeriesTimerInfoDto(i.Item1, i.Item2, channelName); - }) .ToArray(); @@ -1878,7 +1874,6 @@ namespace Emby.Server.Implementations.LiveTv OrderBy = new[] { (ItemSortBy.StartDate, SortOrder.Ascending) }, TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id }, DtoOptions = options - }) : new List(); RemoveFields(options); @@ -1956,7 +1951,7 @@ namespace Emby.Server.Implementations.LiveTv OriginalAirDate = program.PremiereDate, Overview = program.Overview, StartDate = program.StartDate, - //ImagePath = program.ExternalImagePath, + // ImagePath = program.ExternalImagePath, Name = program.Name, OfficialRating = program.OfficialRating }; @@ -2167,20 +2162,19 @@ namespace Emby.Server.Implementations.LiveTv var info = new LiveTvInfo { Services = services, - IsEnabled = services.Length > 0 + IsEnabled = services.Length > 0, + EnabledUsers = _userManager.Users + .Where(IsLiveTvEnabled) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) + .ToArray() }; - info.EnabledUsers = _userManager.Users - .Where(IsLiveTvEnabled) - .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) - .ToArray(); - return info; } private bool IsLiveTvEnabled(User user) { - return user.Policy.EnableLiveTvAccess && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0); + return user.HasPermission(PermissionKind.EnableLiveTvAccess) && (Services.Count > 1 || GetConfiguration().TunerHosts.Length > 0); } public IEnumerable GetEnabledUsers() @@ -2457,7 +2451,6 @@ namespace Emby.Server.Implementations.LiveTv UserId = user.Id, IsRecordingsFolder = true, RefreshLatestChannelItems = refreshChannels - }).Items); return folders.Cast().ToList(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index ba3594efd..a8d34d19c 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false); var list = result.ToList(); - //logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); + // logger.LogInformation("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list)); if (!string.IsNullOrEmpty(key) && list.Count > 0) { @@ -99,7 +99,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } } } @@ -116,7 +115,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index 25b2c674c..f14fcde2a 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -111,7 +111,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun ChannelType = ChannelType.TV, IsLegacyTuner = (i.URL ?? string.Empty).StartsWith("hdhomerun", StringComparison.OrdinalIgnoreCase), Path = i.URL - }).Cast().ToList(); } @@ -481,7 +480,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun Height = height, BitRate = videoBitrate, NalLengthSize = nal - }, new MediaStream { @@ -502,8 +500,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun SupportsTranscoding = true, IsInfiniteStream = true, IgnoreDts = true, - //IgnoreIndex = true, - //ReadAtNativeFramerate = true + // IgnoreIndex = true, + // ReadAtNativeFramerate = true }; mediaSource.InferTotalBitrate(); @@ -722,7 +720,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun } } } - } catch (OperationCanceledException) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 82b1f3cf1..6730751d5 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -117,17 +117,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun taskCompletionSource, LiveStreamCancellationTokenSource.Token).ConfigureAwait(false); - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = tempFile; - //OpenedMediaSource.ReadAtNativeFramerate = true; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = tempFile; + // OpenedMediaSource.ReadAtNativeFramerate = true; MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; - //OpenedMediaSource.SupportsDirectPlay = false; - //OpenedMediaSource.SupportsDirectStream = true; - //OpenedMediaSource.SupportsTranscoding = true; + // OpenedMediaSource.SupportsDirectPlay = false; + // OpenedMediaSource.SupportsDirectStream = true; + // OpenedMediaSource.SupportsTranscoding = true; - //await Task.Delay(5000).ConfigureAwait(false); + // await Task.Delay(5000).ConfigureAwait(false); await taskCompletionSource.Task.ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 4e4f1d7f6..4decdc24f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -220,11 +220,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } catch (IOException) { - } catch (ArgumentException) { - } catch (Exception ex) { diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index f7c9c736e..ff42a9747 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false)) { - } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index 59451fccd..c798c0a85 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -210,7 +210,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts } } } - } if (!IsValidChannelNumber(numberString)) @@ -284,7 +283,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number)) { - //channel.Number = number.ToString(); + // channel.Number = number.ToString(); nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' }); } } diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 322fbbbaa..bc4dcd894 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -103,21 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = tempFile; - //OpenedMediaSource.ReadAtNativeFramerate = true; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = tempFile; + // OpenedMediaSource.ReadAtNativeFramerate = true; MediaSource.Path = _appHost.GetLoopbackHttpApiUrl() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts"; MediaSource.Protocol = MediaProtocol.Http; - //OpenedMediaSource.Path = TempFilePath; - //OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.Path = TempFilePath; + // OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.Path = _tempFilePath; - //OpenedMediaSource.Protocol = MediaProtocol.File; - //OpenedMediaSource.SupportsDirectPlay = false; - //OpenedMediaSource.SupportsDirectStream = true; - //OpenedMediaSource.SupportsTranscoding = true; + // OpenedMediaSource.Path = _tempFilePath; + // OpenedMediaSource.Protocol = MediaProtocol.File; + // OpenedMediaSource.SupportsDirectPlay = false; + // OpenedMediaSource.SupportsDirectStream = true; + // OpenedMediaSource.SupportsTranscoding = true; await taskCompletionSource.Task.ConfigureAwait(false); if (taskCompletionSource.Task.Exception != null) { diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index f347540c7..177721658 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -98,7 +98,6 @@ namespace Emby.Server.Implementations.Net } catch (SocketException) { - } try @@ -109,12 +108,11 @@ namespace Emby.Server.Implementations.Net } catch (SocketException) { - } try { - //retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + // retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); var localIp = IPAddress.Any; diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index 45864bb42..2909cdd0d 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -167,7 +167,7 @@ namespace Emby.Server.Implementations.Networking foreach (var subnet_Match in subnets) { - //logger.LogDebug("subnet_Match:" + subnet_Match); + // logger.LogDebug("subnet_Match:" + subnet_Match); if (endpoint.StartsWith(subnet_Match + ".", StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs index 889760586..358606b0d 100644 --- a/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs +++ b/Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; @@ -44,7 +45,7 @@ namespace Emby.Server.Implementations.Playlists } query.Recursive = true; - query.IncludeItemTypes = new string[] { "Playlist" }; + query.IncludeItemTypes = new[] { "Playlist" }; query.Parent = null; return LibraryManager.GetItemsResult(query); } diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs index 8d022d6c4..9feef5d8d 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -21,6 +22,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using PlaylistsNET.Content; using PlaylistsNET.Models; +using Genre = MediaBrowser.Controller.Entities.Genre; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace Emby.Server.Implementations.Playlists { diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 9028222dd..3854be703 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -163,7 +163,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (ObjectDisposedException) { - //TODO Investigate and properly fix. + // TODO Investigate and properly fix. break; } } diff --git a/Emby.Server.Implementations/Security/AuthenticationRepository.cs b/Emby.Server.Implementations/Security/AuthenticationRepository.cs index 750890ec8..9c1be9a1a 100644 --- a/Emby.Server.Implementations/Security/AuthenticationRepository.cs +++ b/Emby.Server.Implementations/Security/AuthenticationRepository.cs @@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.Security AddColumn(db, "AccessTokens", "UserName", "TEXT", existingColumnNames); AddColumn(db, "AccessTokens", "DateLastActivity", "DATETIME", existingColumnNames); AddColumn(db, "AccessTokens", "AppVersion", "TEXT", existingColumnNames); - }, TransactionMode); connection.RunQueries(new[] @@ -107,7 +106,6 @@ namespace Emby.Server.Implementations.Security statement.MoveNext(); } - }, TransactionMode); } } @@ -367,7 +365,6 @@ namespace Emby.Server.Implementations.Security return result; } - }, ReadTransactionMode); } } @@ -398,7 +395,6 @@ namespace Emby.Server.Implementations.Security statement.MoveNext(); } - }, TransactionMode); } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index f2b1d06f3..3f1672e94 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Services } } - //ContentType='text/html' is the default for a HttpResponse - //Do not override if another has been set + // ContentType='text/html' is the default for a HttpResponse + // Do not override if another has been set if (response.ContentType == null || response.ContentType == "text/html") { response.ContentType = defaultContentType; diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs index e688278b5..b84e47140 100644 --- a/Emby.Server.Implementations/Services/ServiceController.cs +++ b/Emby.Server.Implementations/Services/ServiceController.cs @@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Services ServiceExecGeneral.CreateServiceRunnersFor(requestType, actions); - //var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>)); - //var responseType = returnMarker != null ? + // var returnMarker = GetTypeWithGenericTypeDefinitionOf(requestType, typeof(IReturn<>)); + // var responseType = returnMarker != null ? // GetGenericArguments(returnMarker)[0] // : mi.ReturnType != typeof(object) && mi.ReturnType != typeof(void) ? // mi.ReturnType @@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Services serviceRequiresContext.Request = req; } - //Executes the service and returns the result + // Executes the service and returns the result return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()); } } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 278379a92..43869f98a 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.Services var componentsList = new List(); - //We only split on '.' if the restPath has them. Allows for /{action}.{type} + // We only split on '.' if the restPath has them. Allows for /{action}.{type} var hasSeparators = new List(); foreach (var component in this.restPath.Split(PathSeperatorChar)) { @@ -298,12 +298,12 @@ namespace Emby.Server.Implementations.Services return -1; } - //Routes with least wildcard matches get the highest score + // Routes with least wildcard matches get the highest score var score = Math.Max((100 - wildcardMatchCount), 1) * 1000 - //Routes with less variable (and more literal) matches + // Routes with less variable (and more literal) matches + Math.Max((10 - VariableArgsCount), 1) * 100; - //Exact verb match is better than ANY + // Exact verb match is better than ANY if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase)) { score += 10; @@ -470,7 +470,7 @@ namespace Emby.Server.Implementations.Services + variableName + " on " + RequestType.GetMethodName()); } - var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; //wildcard has arg mismatch + var value = requestComponents.Length > pathIx ? requestComponents[pathIx] : null; // wildcard has arg mismatch if (value != null && this.isWildcard[i]) { if (i == this.TotalComponentsCount - 1) @@ -519,8 +519,8 @@ namespace Emby.Server.Implementations.Services if (queryStringAndFormData != null) { - //Query String and form data can override variable path matches - //path variables < query string < form data + // Query String and form data can override variable path matches + // path variables < query string < form data foreach (var name in queryStringAndFormData) { requestKeyValuesMap[name.Key] = name.Value; diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs index ab22fe019..d3d27ae58 100644 --- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs +++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Services if (propertySerializerEntry.PropertyType == typeof(bool)) { - //InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value + // InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString(); } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 506e6739f..07e443ef5 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -7,6 +7,8 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller; @@ -15,7 +17,6 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; @@ -28,7 +29,9 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using MediaBrowser.Model.SyncPlay; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; namespace Emby.Server.Implementations.Session { @@ -283,11 +286,18 @@ namespace Emby.Server.Implementations.Session if (user != null) { var userLastActivityDate = user.LastActivityDate ?? DateTime.MinValue; - user.LastActivityDate = activityDate; if ((activityDate - userLastActivityDate).TotalSeconds > 60) { - _userManager.UpdateUser(user); + try + { + user.LastActivityDate = activityDate; + _userManager.UpdateUser(user); + } + catch (DbUpdateConcurrencyException e) + { + _logger.LogWarning(e, "Error updating user's last activity date."); + } } } @@ -434,7 +444,13 @@ namespace Emby.Server.Implementations.Session /// The remote end point. /// The user. /// SessionInfo. - private SessionInfo GetSessionInfo(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo GetSessionInfo( + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { CheckDisposed(); @@ -447,14 +463,13 @@ namespace Emby.Server.Implementations.Session CheckDisposed(); - var sessionInfo = _activeConnections.GetOrAdd(key, k => - { - return CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user); - }); + var sessionInfo = _activeConnections.GetOrAdd( + key, + k => CreateSession(k, appName, appVersion, deviceId, deviceName, remoteEndPoint, user)); - sessionInfo.UserId = user == null ? Guid.Empty : user.Id; - sessionInfo.UserName = user?.Name; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.UserId = user?.Id ?? Guid.Empty; + sessionInfo.UserName = user?.Username; + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -473,7 +488,14 @@ namespace Emby.Server.Implementations.Session return sessionInfo; } - private SessionInfo CreateSession(string key, string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user) + private SessionInfo CreateSession( + string key, + string appName, + string appVersion, + string deviceId, + string deviceName, + string remoteEndPoint, + User user) { var sessionInfo = new SessionInfo(this, _logger) { @@ -483,11 +505,11 @@ namespace Emby.Server.Implementations.Session Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) }; - var username = user?.Name; + var username = user?.Username; sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; - sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); + sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user); sessionInfo.RemoteEndPoint = remoteEndPoint; if (string.IsNullOrEmpty(deviceName)) @@ -535,10 +557,7 @@ namespace Emby.Server.Implementations.Session private void StartIdleCheckTimer() { - if (_idleTimer == null) - { - _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); - } + _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } private void StopIdleCheckTimer() @@ -786,7 +805,7 @@ namespace Emby.Server.Implementations.Session { var changed = false; - if (user.Configuration.RememberAudioSelections) + if (user.RememberAudioSelections) { if (data.AudioStreamIndex != info.AudioStreamIndex) { @@ -803,7 +822,7 @@ namespace Emby.Server.Implementations.Session } } - if (user.Configuration.RememberSubtitleSelections) + if (user.RememberSubtitleSelections) { if (data.SubtitleStreamIndex != info.SubtitleStreamIndex) { @@ -1114,13 +1133,13 @@ namespace Emby.Server.Implementations.Session if (items.Any(i => i.GetPlayAccess(user) != PlayAccess.Full)) { throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Name)); + string.Format(CultureInfo.InvariantCulture, "{0} is not allowed to play media.", user.Username)); } } if (user != null && command.ItemIds.Length == 1 - && user.Configuration.EnableNextEpisodeAutoPlay + && user.EnableNextEpisodeAutoPlay && _libraryManager.GetItemById(command.ItemIds[0]) is Episode episode) { var series = episode.Series; @@ -1191,7 +1210,7 @@ namespace Emby.Server.Implementations.Session DtoOptions = new DtoOptions(false) { EnableImages = false, - Fields = new ItemFields[] + Fields = new[] { ItemFields.SortName } @@ -1353,7 +1372,7 @@ namespace Emby.Server.Implementations.Session list.Add(new SessionUserInfo { UserId = userId, - UserName = user.Name + UserName = user.Username }); session.AdditionalUsers = list.ToArray(); @@ -1513,7 +1532,7 @@ namespace Emby.Server.Implementations.Session DeviceName = deviceName, UserId = user.Id, AccessToken = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture), - UserName = user.Name + UserName = user.Username }; _logger.LogInformation("Creating new access token for user {0}", user.Id); @@ -1710,15 +1729,15 @@ namespace Emby.Server.Implementations.Session return info; } - private string GetImageCacheTag(BaseItem item, ImageType type) + private string GetImageCacheTag(User user) { try { - return _imageProcessor.GetImageCacheTag(item, type); + return _imageProcessor.GetImageCacheTag(user); } - catch (Exception ex) + catch (Exception e) { - _logger.LogError(ex, "Error getting image information for {Type}", type); + _logger.LogError(e, "Error getting image information for profile image"); return null; } } @@ -1827,7 +1846,10 @@ namespace Emby.Server.Implementations.Session { CheckDisposed(); - var adminUserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id).ToList(); + var adminUserIds = _userManager.Users + .Where(i => i.HasPermission(PermissionKind.IsAdministrator)) + .Select(i => i.Id) + .ToList(); return SendMessageToUserSessions(adminUserIds, name, data, cancellationToken); } diff --git a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs index 67e31f7f6..2b7d818be 100644 --- a/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs +++ b/Emby.Server.Implementations/Sorting/AiredEpisodeOrderComparer.cs @@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Sorting if (val != 0) { - //return val; + // return val; } } diff --git a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs index 5c1503ed2..03ff19d21 100644 --- a/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs index 73f59f8cd..5e527ea25 100644 --- a/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/DatePlayedComparer.cs @@ -1,4 +1,5 @@ using System; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs index aba14c6ca..0c4e82d01 100644 --- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs index 39d9bc68e..d95948406 100644 --- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs index 478df4035..1632c5a7a 100644 --- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs +++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs @@ -1,5 +1,6 @@ #pragma warning disable CS1591 +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs index eb74ce1bd..afbaaf6ab 100644 --- a/Emby.Server.Implementations/Sorting/PlayCountComparer.cs +++ b/Emby.Server.Implementations/Sorting/PlayCountComparer.cs @@ -1,3 +1,4 @@ +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sorting; diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs index d0812a13f..b1f8fd330 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs @@ -102,20 +102,15 @@ namespace Emby.Server.Implementations.SyncPlay return new SessionInfo[] { from }; case BroadcastType.AllGroup: return _group.Participants.Values.Select( - session => session.Session - ).ToArray(); + session => session.Session).ToArray(); case BroadcastType.AllExceptCurrentSession: return _group.Participants.Values.Select( - session => session.Session - ).Where( - session => !session.Id.Equals(from.Id) - ).ToArray(); + session => session.Session).Where( + session => !session.Id.Equals(from.Id)).ToArray(); case BroadcastType.AllReady: return _group.Participants.Values.Where( - session => !session.IsBuffering - ).Select( - session => session.Session - ).ToArray(); + session => !session.IsBuffering).Select( + session => session.Session).ToArray(); default: return Array.Empty(); } @@ -314,8 +309,7 @@ namespace Emby.Server.Implementations.SyncPlay // Playback synchronization will mainly happen client side _group.IsPaused = false; _group.LastActivity = DateTime.UtcNow.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); @@ -449,8 +443,7 @@ namespace Emby.Server.Implementations.SyncPlay { // Client that was buffering is recovering, notifying others to resume _group.LastActivity = currentTime.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllExceptCurrentSession, command, cancellationToken); } @@ -461,8 +454,7 @@ namespace Emby.Server.Implementations.SyncPlay delay = delay < _group.DefaulPing ? _group.DefaulPing : delay; _group.LastActivity = currentTime.AddMilliseconds( - delay - ); + delay); var command = NewSyncPlayCommand(SendCommandType.Play); SendCommand(session, BroadcastType.AllGroup, command, cancellationToken); diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 1bfc9a9a5..45a43fd78 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; -using Microsoft.Extensions.Logging; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Session; using MediaBrowser.Controller.SyncPlay; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.SyncPlay; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SyncPlay { @@ -109,14 +109,6 @@ namespace Emby.Server.Implementations.SyncPlay _disposed = true; } - private void CheckDisposed() - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - } - private void OnSessionManagerSessionEnded(object sender, SessionEventArgs e) { var session = e.SessionInfo; @@ -149,38 +141,24 @@ namespace Emby.Server.Implementations.SyncPlay var item = _libraryManager.GetItemById(itemId); // Check ParentalRating access - var hasParentalRatingAccess = true; - if (user.Policy.MaxParentalRating.HasValue) - { - hasParentalRatingAccess = item.InheritedParentalRatingValue <= user.Policy.MaxParentalRating; - } + var hasParentalRatingAccess = !user.MaxParentalAgeRating.HasValue + || item.InheritedParentalRatingValue <= user.MaxParentalAgeRating; - if (!user.Policy.EnableAllFolders && hasParentalRatingAccess) + if (!user.HasPermission(PermissionKind.EnableAllFolders) && hasParentalRatingAccess) { var collections = _libraryManager.GetCollectionFolders(item).Select( - folder => folder.Id.ToString("N", CultureInfo.InvariantCulture) - ); - var intersect = collections.Intersect(user.Policy.EnabledFolders); - return intersect.Any(); - } - else - { - return hasParentalRatingAccess; + folder => folder.Id.ToString("N", CultureInfo.InvariantCulture)); + + return collections.Intersect(user.GetPreference(PreferenceKind.EnabledFolders)).Any(); } + + return hasParentalRatingAccess; } private Guid? GetSessionGroup(SessionInfo session) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); - if (group != null) - { - return group.GetGroupId(); - } - else - { - return null; - } + _sessionToGroupMap.TryGetValue(session.Id, out var group); + return group?.GetGroupId(); } /// @@ -188,7 +166,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) + if (user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups) { _logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id); @@ -196,7 +174,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.CreateGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -219,7 +197,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("JoinGroup: {0} does not have access to SyncPlay.", session.Id); @@ -227,7 +205,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -244,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.GroupDoesNotExist }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -257,7 +235,7 @@ namespace Emby.Server.Implementations.SyncPlay GroupId = group.GetGroupId().ToString(), Type = GroupUpdateType.LibraryAccessDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -281,8 +259,7 @@ namespace Emby.Server.Implementations.SyncPlay // TODO: determine what happens to users that are in a group and get their permissions revoked lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -292,7 +269,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -311,7 +288,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { return new List(); } @@ -320,19 +297,15 @@ namespace Emby.Server.Implementations.SyncPlay if (!filterItemId.Equals(Guid.Empty)) { return _groups.Values.Where( - group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); + group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select( + group => group.GetInfo()).ToList(); } // Otherwise show all available groups else { return _groups.Values.Where( - group => HasAccessToItem(user, group.GetPlayingItemId()) - ).Select( - group => group.GetInfo() - ).ToList(); + group => HasAccessToItem(user, group.GetPlayingItemId())).Select( + group => group.GetInfo()).ToList(); } } @@ -341,7 +314,7 @@ namespace Emby.Server.Implementations.SyncPlay { var user = _userManager.GetUserById(session.UserId); - if (user.Policy.SyncPlayAccess == SyncPlayAccess.None) + if (user.SyncPlayAccess == SyncPlayAccess.None) { _logger.LogWarning("HandleRequest: {0} does not have access to SyncPlay.", session.Id); @@ -349,14 +322,13 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.JoinGroupDenied }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } lock (_groupsLock) { - ISyncPlayController group; - _sessionToGroupMap.TryGetValue(session.Id, out group); + _sessionToGroupMap.TryGetValue(session.Id, out var group); if (group == null) { @@ -366,7 +338,7 @@ namespace Emby.Server.Implementations.SyncPlay { Type = GroupUpdateType.NotInGroup }; - _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), error, CancellationToken.None); + _sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None); return; } @@ -393,8 +365,7 @@ namespace Emby.Server.Implementations.SyncPlay throw new InvalidOperationException("Session not in any group!"); } - ISyncPlayController tempGroup; - _sessionToGroupMap.Remove(session.Id, out tempGroup); + _sessionToGroupMap.Remove(session.Id, out var tempGroup); if (!tempGroup.GetGroupId().Equals(group.GetGroupId())) { diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 383615f74..db056cc38 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -4,13 +4,17 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace Emby.Server.Implementations.TV { @@ -73,7 +77,8 @@ namespace Emby.Server.Implementations.TV { parents = _libraryManager.GetUserRootFolder().GetChildren(user, true) .Where(i => i is Folder) - .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) + .Where(i => !user.GetPreference(PreferenceKind.LatestItemExcludes) + .Contains(i.Id.ToString("N", CultureInfo.InvariantCulture))) .ToArray(); } @@ -144,7 +149,7 @@ namespace Emby.Server.Implementations.TV var allNextUp = seriesKeys .Select(i => GetNextUp(i, currentUser, dtoOptions)); - //allNextUp = allNextUp.OrderByDescending(i => i.Item1); + // allNextUp = allNextUp.OrderByDescending(i => i.Item1); // If viewing all next up for all series, remove first episodes // But if that returns empty, keep those first episodes (avoid completely empty view) @@ -191,7 +196,7 @@ namespace Emby.Server.Implementations.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { typeof(Episode).Name }, + IncludeItemTypes = new[] { nameof(Episode) }, OrderBy = new[] { new ValueTuple(ItemSortBy.SortName, SortOrder.Descending) }, IsPlayed = true, Limit = 1, @@ -204,7 +209,6 @@ namespace Emby.Server.Implementations.TV }, EnableImages = false } - }).FirstOrDefault(); Func getEpisode = () => @@ -219,9 +223,8 @@ namespace Emby.Server.Implementations.TV IsPlayed = false, IsVirtualItem = false, ParentIndexNumberNotEquals = 0, - MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName, + MinSortName = lastWatchedEpisode?.SortName, DtoOptions = dtoOptions - }).Cast().FirstOrDefault(); }; diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index aab1141ee..767ba9fd4 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; using Jellyfin.Api.Constants; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; @@ -49,10 +50,10 @@ namespace Jellyfin.Api.Auth var claims = new[] { - new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Name, user.Username), new Claim( ClaimTypes.Role, - value: user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User) + value: user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index afc9b8f3d..6ec0a4e26 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -95,10 +95,12 @@ namespace Jellyfin.Api.Controllers [HttpGet("User")] public StartupUserDto GetFirstUser() { + // TODO: Remove this method when startup wizard no longer requires an existing user. + _userManager.Initialize(); var user = _userManager.Users.First(); return new StartupUserDto { - Name = user.Name, + Name = user.Username, Password = user.Password }; } @@ -113,9 +115,9 @@ namespace Jellyfin.Api.Controllers { var user = _userManager.Users.First(); - user.Name = startupUserDto.Name; + user.Username = startupUserDto.Name; - _userManager.UpdateUser(user); + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); if (!string.IsNullOrEmpty(startupUserDto.Password)) { diff --git a/Jellyfin.Data/DayOfWeekHelper.cs b/Jellyfin.Data/DayOfWeekHelper.cs new file mode 100644 index 000000000..32a41368d --- /dev/null +++ b/Jellyfin.Data/DayOfWeekHelper.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data +{ + public static class DayOfWeekHelper + { + public static List GetDaysOfWeek(DynamicDayOfWeek day) + { + var days = new List(7); + + if (day == DynamicDayOfWeek.Sunday + || day == DynamicDayOfWeek.Weekend + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Sunday); + } + + if (day == DynamicDayOfWeek.Monday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Monday); + } + + if (day == DynamicDayOfWeek.Tuesday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Tuesday); + } + + if (day == DynamicDayOfWeek.Wednesday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Wednesday); + } + + if (day == DynamicDayOfWeek.Thursday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Thursday); + } + + if (day == DynamicDayOfWeek.Friday + || day == DynamicDayOfWeek.Weekday + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Friday); + } + + if (day == DynamicDayOfWeek.Saturday + || day == DynamicDayOfWeek.Weekend + || day == DynamicDayOfWeek.Everyday) + { + days.Add(DayOfWeek.Saturday); + } + + return days; + } + } +} diff --git a/Jellyfin.Data/Entities/AccessSchedule.cs b/Jellyfin.Data/Entities/AccessSchedule.cs new file mode 100644 index 000000000..7d1b76a3f --- /dev/null +++ b/Jellyfin.Data/Entities/AccessSchedule.cs @@ -0,0 +1,91 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; +using System.Xml.Serialization; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data.Entities +{ + /// + /// An entity representing a user's access schedule. + /// + public class AccessSchedule + { + /// + /// Initializes a new instance of the class. + /// + /// The day of the week. + /// The start hour. + /// The end hour. + /// The associated user's id. + public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) + { + UserId = userId; + DayOfWeek = dayOfWeek; + StartHour = startHour; + EndHour = endHour; + } + + /// + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. + /// + protected AccessSchedule() + { + } + + /// + /// Gets or sets the id of this instance. + /// + /// + /// Identity, Indexed, Required. + /// + [XmlIgnore] + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + /// + /// Gets or sets the id of the associated user. + /// + [XmlIgnore] + [Required] + public Guid UserId { get; protected set; } + + /// + /// Gets or sets the day of week. + /// + /// The day of week. + [Required] + public DynamicDayOfWeek DayOfWeek { get; set; } + + /// + /// Gets or sets the start hour. + /// + /// The start hour. + [Required] + public double StartHour { get; set; } + + /// + /// Gets or sets the end hour. + /// + /// The end hour. + [Required] + public double EndHour { get; set; } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The day of the week. + /// The start hour. + /// The end hour. + /// The associated user's id. + /// The newly created instance. + public static AccessSchedule Create(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId) + { + return new AccessSchedule(dayOfWeek, startHour, endHour, userId); + } + } +} diff --git a/Jellyfin.Data/Entities/Artwork.cs b/Jellyfin.Data/Entities/Artwork.cs index bf3029368..214fb4cb1 100644 --- a/Jellyfin.Data/Entities/Artwork.cs +++ b/Jellyfin.Data/Entities/Artwork.cs @@ -189,7 +189,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Book.cs b/Jellyfin.Data/Entities/Book.cs index 42d24e31d..faefc7400 100644 --- a/Jellyfin.Data/Entities/Book.cs +++ b/Jellyfin.Data/Entities/Book.cs @@ -63,7 +63,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Release_Releases_Id")] public virtual ICollection Releases { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/BookMetadata.cs b/Jellyfin.Data/Entities/BookMetadata.cs index d52fe7605..dd389b64a 100644 --- a/Jellyfin.Data/Entities/BookMetadata.cs +++ b/Jellyfin.Data/Entities/BookMetadata.cs @@ -101,7 +101,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Company_Publishers_Id")] public virtual ICollection Publishers { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/Chapter.cs b/Jellyfin.Data/Entities/Chapter.cs index d48cb9b62..9b3a5e827 100644 --- a/Jellyfin.Data/Entities/Chapter.cs +++ b/Jellyfin.Data/Entities/Chapter.cs @@ -257,7 +257,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Collection.cs b/Jellyfin.Data/Entities/Collection.cs index e2fa3a5bd..c040cfe33 100644 --- a/Jellyfin.Data/Entities/Collection.cs +++ b/Jellyfin.Data/Entities/Collection.cs @@ -114,7 +114,6 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("CollectionItem_CollectionItem_Id")] public virtual ICollection CollectionItem { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/CollectionItem.cs b/Jellyfin.Data/Entities/CollectionItem.cs index 4a3d06639..c5e54c3a2 100644 --- a/Jellyfin.Data/Entities/CollectionItem.cs +++ b/Jellyfin.Data/Entities/CollectionItem.cs @@ -137,7 +137,6 @@ namespace Jellyfin.Data.Entities /// [ForeignKey("CollectionItem_Previous_Id")] public virtual CollectionItem Previous { get; set; } - } } diff --git a/Jellyfin.Data/Entities/Company.cs b/Jellyfin.Data/Entities/Company.cs index 0650271c6..7d6f3b207 100644 --- a/Jellyfin.Data/Entities/Company.cs +++ b/Jellyfin.Data/Entities/Company.cs @@ -131,7 +131,6 @@ namespace Jellyfin.Data.Entities public virtual ICollection CompanyMetadata { get; protected set; } [ForeignKey("Company_Parent_Id")] public virtual Company Parent { get; set; } - } } diff --git a/Jellyfin.Data/Entities/CompanyMetadata.cs b/Jellyfin.Data/Entities/CompanyMetadata.cs index b3ec9c1a7..1ad03b4f9 100644 --- a/Jellyfin.Data/Entities/CompanyMetadata.cs +++ b/Jellyfin.Data/Entities/CompanyMetadata.cs @@ -210,7 +210,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/CustomItem.cs b/Jellyfin.Data/Entities/CustomItem.cs index 2006717bf..5f6fc3a23 100644 --- a/Jellyfin.Data/Entities/CustomItem.cs +++ b/Jellyfin.Data/Entities/CustomItem.cs @@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Release_Releases_Id")] public virtual ICollection Releases { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/CustomItemMetadata.cs b/Jellyfin.Data/Entities/CustomItemMetadata.cs index e09e4467a..ee37aaaa9 100644 --- a/Jellyfin.Data/Entities/CustomItemMetadata.cs +++ b/Jellyfin.Data/Entities/CustomItemMetadata.cs @@ -61,7 +61,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Episode.cs b/Jellyfin.Data/Entities/Episode.cs index 6f6baa14d..88531205f 100644 --- a/Jellyfin.Data/Entities/Episode.cs +++ b/Jellyfin.Data/Entities/Episode.cs @@ -104,7 +104,6 @@ namespace Jellyfin.Data.Entities public virtual ICollection Releases { get; protected set; } [ForeignKey("EpisodeMetadata_EpisodeMetadata_Id")] public virtual ICollection EpisodeMetadata { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/EpisodeMetadata.cs b/Jellyfin.Data/Entities/EpisodeMetadata.cs index e5431bf22..0aa4b4270 100644 --- a/Jellyfin.Data/Entities/EpisodeMetadata.cs +++ b/Jellyfin.Data/Entities/EpisodeMetadata.cs @@ -173,7 +173,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Genre.cs b/Jellyfin.Data/Entities/Genre.cs index 38f289a8e..ff0710671 100644 --- a/Jellyfin.Data/Entities/Genre.cs +++ b/Jellyfin.Data/Entities/Genre.cs @@ -146,7 +146,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Group.cs b/Jellyfin.Data/Entities/Group.cs index 54f9f4905..5cbb126f9 100644 --- a/Jellyfin.Data/Entities/Group.cs +++ b/Jellyfin.Data/Entities/Group.cs @@ -2,19 +2,32 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class Group + /// + /// An entity representing a group. + /// + public partial class Group : IHasPermissions, ISavingChanges { - partial void Init(); - /// - /// Default constructor. Protected due to required properties, but present because EF needs it. + /// Initializes a new instance of the class. + /// Public constructor with required data. /// - protected Group() + /// The name of the group. + public Group(string name) { - GroupPermissions = new HashSet(); + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + Name = name; + Id = Guid.NewGuid(); + + Permissions = new HashSet(); ProviderMappings = new HashSet(); Preferences = new HashSet(); @@ -22,66 +35,45 @@ namespace Jellyfin.Data.Entities } /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. + /// Initializes a new instance of the class. + /// Default constructor. Protected due to required properties, but present because EF needs it. /// - public static Group CreateGroupUnsafe() + protected Group() { - return new Group(); - } - - /// - /// Public constructor with required data - /// - /// - /// - public Group(string name, User _user0) - { - if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); - this.Name = name; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Groups.Add(this); - - this.GroupPermissions = new HashSet(); - this.ProviderMappings = new HashSet(); - this.Preferences = new HashSet(); - Init(); } - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - public static Group Create(string name, User _user0) - { - return new Group(name, _user0); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this group. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + public Guid Id { get; protected set; } /// - /// Required, Max length = 255 + /// Gets or sets the group's name. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string Name { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets the row version. /// + /// + /// Required, Concurrency Token. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } @@ -96,7 +88,7 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("Permission_GroupPermissions_Id")] - public virtual ICollection GroupPermissions { get; protected set; } + public virtual ICollection Permissions { get; protected set; } [ForeignKey("ProviderMapping_ProviderMappings_Id")] public virtual ICollection ProviderMappings { get; protected set; } @@ -104,6 +96,27 @@ namespace Jellyfin.Data.Entities [ForeignKey("Preference_Preferences_Id")] public virtual ICollection Preferences { get; protected set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The name of this group + public static Group Create(string name) + { + return new Group(name); + } + + /// + public bool HasPermission(PermissionKind kind) + { + return Permissions.First(p => p.Kind == kind).Value; + } + + /// + public void SetPermission(PermissionKind kind, bool value) + { + Permissions.First(p => p.Kind == kind).Value = value; + } + + partial void Init(); } } - diff --git a/Jellyfin.Data/Entities/ImageInfo.cs b/Jellyfin.Data/Entities/ImageInfo.cs new file mode 100644 index 000000000..64e36a791 --- /dev/null +++ b/Jellyfin.Data/Entities/ImageInfo.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Jellyfin.Data.Entities +{ + public class ImageInfo + { + public ImageInfo(string path) + { + Path = path; + LastModified = DateTime.UtcNow; + } + + [Key] + [Required] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; protected set; } + + public Guid? UserId { get; protected set; } + + [Required] + [MaxLength(512)] + [StringLength(512)] + public string Path { get; set; } + + [Required] + public DateTime LastModified { get; set; } + } +} diff --git a/Jellyfin.Data/Entities/Library.cs b/Jellyfin.Data/Entities/Library.cs index c11c09e91..a5cc5c8da 100644 --- a/Jellyfin.Data/Entities/Library.cs +++ b/Jellyfin.Data/Entities/Library.cs @@ -141,7 +141,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/LibraryItem.cs b/Jellyfin.Data/Entities/LibraryItem.cs index af6c640b9..c2ba7059d 100644 --- a/Jellyfin.Data/Entities/LibraryItem.cs +++ b/Jellyfin.Data/Entities/LibraryItem.cs @@ -164,7 +164,6 @@ namespace Jellyfin.Data.Entities /// [ForeignKey("LibraryRoot_Id")] public virtual LibraryRoot LibraryRoot { get; set; } - } } diff --git a/Jellyfin.Data/Entities/LibraryRoot.cs b/Jellyfin.Data/Entities/LibraryRoot.cs index bbc23e1c9..7823db02a 100644 --- a/Jellyfin.Data/Entities/LibraryRoot.cs +++ b/Jellyfin.Data/Entities/LibraryRoot.cs @@ -186,7 +186,6 @@ namespace Jellyfin.Data.Entities /// [ForeignKey("Library_Id")] public virtual Library Library { get; set; } - } } diff --git a/Jellyfin.Data/Entities/MediaFile.cs b/Jellyfin.Data/Entities/MediaFile.cs index 719539e5c..94c39a28a 100644 --- a/Jellyfin.Data/Entities/MediaFile.cs +++ b/Jellyfin.Data/Entities/MediaFile.cs @@ -194,7 +194,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("MediaFileStream_MediaFileStreams_Id")] public virtual ICollection MediaFileStreams { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/MediaFileStream.cs b/Jellyfin.Data/Entities/MediaFileStream.cs index 7b3399731..723977fdf 100644 --- a/Jellyfin.Data/Entities/MediaFileStream.cs +++ b/Jellyfin.Data/Entities/MediaFileStream.cs @@ -143,7 +143,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Metadata.cs b/Jellyfin.Data/Entities/Metadata.cs index 467ee6822..6558642cf 100644 --- a/Jellyfin.Data/Entities/Metadata.cs +++ b/Jellyfin.Data/Entities/Metadata.cs @@ -374,7 +374,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("PersonRole_PersonRoles_Id")] public virtual ICollection Sources { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/MetadataProvider.cs b/Jellyfin.Data/Entities/MetadataProvider.cs index 4e4f107fb..bf9689709 100644 --- a/Jellyfin.Data/Entities/MetadataProvider.cs +++ b/Jellyfin.Data/Entities/MetadataProvider.cs @@ -141,7 +141,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/MetadataProviderId.cs b/Jellyfin.Data/Entities/MetadataProviderId.cs index 926f223de..c49c6f42e 100644 --- a/Jellyfin.Data/Entities/MetadataProviderId.cs +++ b/Jellyfin.Data/Entities/MetadataProviderId.cs @@ -173,7 +173,6 @@ namespace Jellyfin.Data.Entities /// [ForeignKey("MetadataProvider_Id")] public virtual MetadataProvider MetadataProvider { get; set; } - } } diff --git a/Jellyfin.Data/Entities/Movie.cs b/Jellyfin.Data/Entities/Movie.cs index b359b42fc..ad2504b0d 100644 --- a/Jellyfin.Data/Entities/Movie.cs +++ b/Jellyfin.Data/Entities/Movie.cs @@ -63,7 +63,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("MovieMetadata_MovieMetadata_Id")] public virtual ICollection MovieMetadata { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/MovieMetadata.cs b/Jellyfin.Data/Entities/MovieMetadata.cs index 319ae94e5..1f8f1c2a0 100644 --- a/Jellyfin.Data/Entities/MovieMetadata.cs +++ b/Jellyfin.Data/Entities/MovieMetadata.cs @@ -217,7 +217,6 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("Company_Studios_Id")] public virtual ICollection Studios { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/MusicAlbum.cs b/Jellyfin.Data/Entities/MusicAlbum.cs index 00cb8fe00..e07f4357b 100644 --- a/Jellyfin.Data/Entities/MusicAlbum.cs +++ b/Jellyfin.Data/Entities/MusicAlbum.cs @@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Track_Tracks_Id")] public virtual ICollection Tracks { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs index b52ca6564..7743890a6 100644 --- a/Jellyfin.Data/Entities/MusicAlbumMetadata.cs +++ b/Jellyfin.Data/Entities/MusicAlbumMetadata.cs @@ -181,7 +181,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Company_Labels_Id")] public virtual ICollection Labels { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/Permission.cs b/Jellyfin.Data/Entities/Permission.cs index 0b5b52cbd..b675e911d 100644 --- a/Jellyfin.Data/Entities/Permission.cs +++ b/Jellyfin.Data/Entities/Permission.cs @@ -1,16 +1,30 @@ -using System; -using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Runtime.CompilerServices; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class Permission + /// + /// An entity representing whether the associated user has a specific permission. + /// + public partial class Permission : ISavingChanges { - partial void Init(); + /// + /// Initializes a new instance of the class. + /// Public constructor with required data. + /// + /// The permission kind. + /// The value of this permission. + public Permission(PermissionKind kind, bool value) + { + Kind = kind; + Value = value; + + Init(); + } /// + /// Initializes a new instance of the class. /// Default constructor. Protected due to required properties, but present because EF needs it. /// protected Permission() @@ -18,127 +32,66 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Permission CreatePermissionUnsafe() - { - return new Permission(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - public Permission(Enums.PermissionKind kind, bool value, User _user0, Group _group1) - { - this.Kind = kind; - - this.Value = value; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Permissions.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.GroupPermissions.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1) - { - return new Permission(kind, value, _user0, _group1); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this permission. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } /// - /// Backing field for Kind - /// - protected Enums.PermissionKind _Kind; - /// - /// When provided in a partial class, allows value of Kind to be changed before setting. - /// - partial void SetKind(Enums.PermissionKind oldValue, ref Enums.PermissionKind newValue); - /// - /// When provided in a partial class, allows value of Kind to be changed before returning. - /// - partial void GetKind(ref Enums.PermissionKind result); - - /// - /// Required + /// Gets or sets the type of this permission. /// + /// + /// Required. + /// [Required] - public Enums.PermissionKind Kind - { - get - { - Enums.PermissionKind value = _Kind; - GetKind(ref value); - return (_Kind = value); - } - set - { - Enums.PermissionKind oldValue = _Kind; - SetKind(oldValue, ref value); - if (oldValue != value) - { - _Kind = value; - OnPropertyChanged(); - } - } - } + public PermissionKind Kind { get; protected set; } /// - /// Required + /// Gets or sets a value indicating whether the associated user has this permission. /// + /// + /// Required. + /// [Required] public bool Value { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets the row version. /// + /// + /// Required, ConcurrencyToken. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The permission kind. + /// The value of this permission. + /// The newly created instance. + public static Permission Create(PermissionKind kind, bool value) + { + return new Permission(kind, value); + } + + /// public void OnSavingChanges() { RowVersion++; } - /************************************************************************* - * Navigation properties - *************************************************************************/ - - public virtual event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - + partial void Init(); } } - diff --git a/Jellyfin.Data/Entities/Person.cs b/Jellyfin.Data/Entities/Person.cs index d893b7e39..f71418819 100644 --- a/Jellyfin.Data/Entities/Person.cs +++ b/Jellyfin.Data/Entities/Person.cs @@ -296,7 +296,6 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("MetadataProviderId_Sources_Id")] public virtual ICollection Sources { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/PersonRole.cs b/Jellyfin.Data/Entities/PersonRole.cs index 9bd12c7fb..a3d047115 100644 --- a/Jellyfin.Data/Entities/PersonRole.cs +++ b/Jellyfin.Data/Entities/PersonRole.cs @@ -203,7 +203,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("MetadataProviderId_Sources_Id")] public virtual ICollection Sources { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/Photo.cs b/Jellyfin.Data/Entities/Photo.cs index 7abe62891..226730126 100644 --- a/Jellyfin.Data/Entities/Photo.cs +++ b/Jellyfin.Data/Entities/Photo.cs @@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Release_Releases_Id")] public virtual ICollection Releases { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/PhotoMetadata.cs b/Jellyfin.Data/Entities/PhotoMetadata.cs index c5502f707..2bb239cdd 100644 --- a/Jellyfin.Data/Entities/PhotoMetadata.cs +++ b/Jellyfin.Data/Entities/PhotoMetadata.cs @@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Preference.cs b/Jellyfin.Data/Entities/Preference.cs index 505f52e6b..0ca9d7eff 100644 --- a/Jellyfin.Data/Entities/Preference.cs +++ b/Jellyfin.Data/Entities/Preference.cs @@ -1,63 +1,33 @@ using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class Preference + /// + /// An entity representing a preference attached to a user or group. + /// + public class Preference : ISavingChanges { - partial void Init(); + /// + /// Initializes a new instance of the class. + /// Public constructor with required data. + /// + /// The preference kind. + /// The value. + public Preference(PreferenceKind kind, string value) + { + Kind = kind; + Value = value ?? throw new ArgumentNullException(nameof(value)); + } /// + /// Initializes a new instance of the class. /// Default constructor. Protected due to required properties, but present because EF needs it. /// protected Preference() { - Init(); - } - - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Preference CreatePreferenceUnsafe() - { - return new Preference(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - public Preference(Enums.PreferenceKind kind, string value, User _user0, Group _group1) - { - this.Kind = kind; - - if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); - this.Value = value; - - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.Preferences.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.Preferences.Add(this); - - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1) - { - return new Preference(kind, value, _user0, _group1); } /************************************************************************* @@ -65,43 +35,61 @@ namespace Jellyfin.Data.Entities *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the id of this preference. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; protected set; } /// - /// Required + /// Gets or sets the type of this preference. /// + /// + /// Required. + /// [Required] - public Enums.PreferenceKind Kind { get; set; } + public PreferenceKind Kind { get; protected set; } /// - /// Required, Max length = 65535 + /// Gets or sets the value of this preference. /// + /// + /// Required, Max length = 65535. + /// [Required] [MaxLength(65535)] [StringLength(65535)] public string Value { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets the row version. /// + /// + /// Required, ConcurrencyToken. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The preference kind. + /// The value. + /// The new instance. + public static Preference Create(PreferenceKind kind, string value) + { + return new Preference(kind, value); + } + + /// public void OnSavingChanges() { RowVersion++; } - - /************************************************************************* - * Navigation properties - *************************************************************************/ - } } - diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs index 6197bd97b..e86d9737f 100644 --- a/Jellyfin.Data/Entities/ProviderMapping.cs +++ b/Jellyfin.Data/Entities/ProviderMapping.cs @@ -43,12 +43,6 @@ namespace Jellyfin.Data.Entities if (string.IsNullOrEmpty(providerdata)) throw new ArgumentNullException(nameof(providerdata)); this.ProviderData = providerdata; - if (_user0 == null) throw new ArgumentNullException(nameof(_user0)); - _user0.ProviderMappings.Add(this); - - if (_group1 == null) throw new ArgumentNullException(nameof(_group1)); - _group1.ProviderMappings.Add(this); - Init(); } @@ -117,7 +111,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Rating.cs b/Jellyfin.Data/Entities/Rating.cs index f70ea8b33..0c8b99ca2 100644 --- a/Jellyfin.Data/Entities/Rating.cs +++ b/Jellyfin.Data/Entities/Rating.cs @@ -181,7 +181,6 @@ namespace Jellyfin.Data.Entities /// [ForeignKey("RatingSource_RatingType_Id")] public virtual RatingSource RatingType { get; set; } - } } diff --git a/Jellyfin.Data/Entities/RatingSource.cs b/Jellyfin.Data/Entities/RatingSource.cs index 070f1ae27..c829042b5 100644 --- a/Jellyfin.Data/Entities/RatingSource.cs +++ b/Jellyfin.Data/Entities/RatingSource.cs @@ -225,7 +225,6 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("MetadataProviderId_Source_Id")] public virtual MetadataProviderId Source { get; set; } - } } diff --git a/Jellyfin.Data/Entities/Release.cs b/Jellyfin.Data/Entities/Release.cs index d1928fcf7..35fcbb4b7 100644 --- a/Jellyfin.Data/Entities/Release.cs +++ b/Jellyfin.Data/Entities/Release.cs @@ -182,7 +182,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Chapter_Chapters_Id")] public virtual ICollection Chapters { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/Season.cs b/Jellyfin.Data/Entities/Season.cs index 96e89cde0..2a861b660 100644 --- a/Jellyfin.Data/Entities/Season.cs +++ b/Jellyfin.Data/Entities/Season.cs @@ -105,7 +105,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Episode_Episodes_Id")] public virtual ICollection Episodes { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/SeasonMetadata.cs b/Jellyfin.Data/Entities/SeasonMetadata.cs index 64ecbfbfa..10320c6bb 100644 --- a/Jellyfin.Data/Entities/SeasonMetadata.cs +++ b/Jellyfin.Data/Entities/SeasonMetadata.cs @@ -100,7 +100,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs index 097b9958e..cf1d6b781 100644 --- a/Jellyfin.Data/Entities/Series.cs +++ b/Jellyfin.Data/Entities/Series.cs @@ -19,14 +19,6 @@ namespace Jellyfin.Data.Entities Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static Series CreateSeriesUnsafe() - { - return new Series(); - } - /// /// Public constructor with required data /// @@ -57,27 +49,27 @@ namespace Jellyfin.Data.Entities /// /// Backing field for AirsDayOfWeek /// - protected Enums.Weekday? _AirsDayOfWeek; + protected DayOfWeek? _AirsDayOfWeek; /// /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting. /// - partial void SetAirsDayOfWeek(Enums.Weekday? oldValue, ref Enums.Weekday? newValue); + partial void SetAirsDayOfWeek(DayOfWeek? oldValue, ref DayOfWeek? newValue); /// /// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning. /// - partial void GetAirsDayOfWeek(ref Enums.Weekday? result); + partial void GetAirsDayOfWeek(ref DayOfWeek? result); - public Enums.Weekday? AirsDayOfWeek + public DayOfWeek? AirsDayOfWeek { get { - Enums.Weekday? value = _AirsDayOfWeek; + DayOfWeek? value = _AirsDayOfWeek; GetAirsDayOfWeek(ref value); return (_AirsDayOfWeek = value); } set { - Enums.Weekday? oldValue = _AirsDayOfWeek; + DayOfWeek? oldValue = _AirsDayOfWeek; SetAirsDayOfWeek(oldValue, ref value); if (oldValue != value) { @@ -161,7 +153,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("Season_Seasons_Id")] public virtual ICollection Seasons { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/SeriesMetadata.cs b/Jellyfin.Data/Entities/SeriesMetadata.cs index 52691783f..bb31c2e4e 100644 --- a/Jellyfin.Data/Entities/SeriesMetadata.cs +++ b/Jellyfin.Data/Entities/SeriesMetadata.cs @@ -217,7 +217,6 @@ namespace Jellyfin.Data.Entities *************************************************************************/ [ForeignKey("Company_Networks_Id")] public virtual ICollection Networks { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/Track.cs b/Jellyfin.Data/Entities/Track.cs index 079d73d2b..c9e8fd1c3 100644 --- a/Jellyfin.Data/Entities/Track.cs +++ b/Jellyfin.Data/Entities/Track.cs @@ -106,7 +106,6 @@ namespace Jellyfin.Data.Entities [ForeignKey("TrackMetadata_TrackMetadata_Id")] public virtual ICollection TrackMetadata { get; protected set; } - } } diff --git a/Jellyfin.Data/Entities/TrackMetadata.cs b/Jellyfin.Data/Entities/TrackMetadata.cs index 86c9161f6..7b99c0683 100644 --- a/Jellyfin.Data/Entities/TrackMetadata.cs +++ b/Jellyfin.Data/Entities/TrackMetadata.cs @@ -62,7 +62,6 @@ namespace Jellyfin.Data.Entities /************************************************************************* * Navigation properties *************************************************************************/ - } } diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index a81d5215b..b89b0a8f4 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -2,234 +2,506 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Globalization; +using System.Linq; +using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; namespace Jellyfin.Data.Entities { - public partial class User + /// + /// An entity representing a user. + /// + public partial class User : IHasPermissions, ISavingChanges { - partial void Init(); + /// + /// The values being delimited here are Guids, so commas work as they do not appear in Guids. + /// + private const char Delimiter = ','; /// + /// Initializes a new instance of the class. + /// Public constructor with required data. + /// + /// The username for the new user. + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. + public User(string username, string authenticationProviderId, string passwordResetProviderId) + { + if (string.IsNullOrEmpty(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + if (string.IsNullOrEmpty(authenticationProviderId)) + { + throw new ArgumentNullException(nameof(authenticationProviderId)); + } + + if (string.IsNullOrEmpty(passwordResetProviderId)) + { + throw new ArgumentNullException(nameof(passwordResetProviderId)); + } + + Username = username; + AuthenticationProviderId = authenticationProviderId; + PasswordResetProviderId = passwordResetProviderId; + + AccessSchedules = new HashSet(); + // Groups = new HashSet(); + Permissions = new HashSet(); + Preferences = new HashSet(); + // ProviderMappings = new HashSet(); + + // Set default values + Id = Guid.NewGuid(); + InvalidLoginAttemptCount = 0; + EnableUserPreferenceAccess = true; + MustUpdatePassword = false; + DisplayMissingEpisodes = false; + DisplayCollectionsView = false; + HidePlayedInLatest = true; + RememberAudioSelections = true; + RememberSubtitleSelections = true; + EnableNextEpisodeAutoPlay = true; + EnableAutoLogin = false; + PlayDefaultAudioTrack = true; + SubtitleMode = SubtitlePlaybackMode.Default; + SyncPlayAccess = SyncPlayAccess.CreateAndJoinGroups; + + AddDefaultPermissions(); + AddDefaultPreferences(); + Init(); + } + + /// + /// Initializes a new instance of the class. /// Default constructor. Protected due to required properties, but present because EF needs it. /// protected User() { - Groups = new HashSet(); - Permissions = new HashSet(); - ProviderMappings = new HashSet(); - Preferences = new HashSet(); - Init(); } - /// - /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving. - /// - public static User CreateUserUnsafe() - { - return new User(); - } - - /// - /// Public constructor with required data - /// - /// - /// - /// - /// - /// - /// - /// - public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) - { - if (string.IsNullOrEmpty(username)) throw new ArgumentNullException(nameof(username)); - this.Username = username; - - this.MustUpdatePassword = mustupdatepassword; - - if (string.IsNullOrEmpty(audiolanguagepreference)) throw new ArgumentNullException(nameof(audiolanguagepreference)); - this.AudioLanguagePreference = audiolanguagepreference; - - if (string.IsNullOrEmpty(authenticationproviderid)) throw new ArgumentNullException(nameof(authenticationproviderid)); - this.AuthenticationProviderId = authenticationproviderid; - - this.InvalidLoginAttemptCount = invalidloginattemptcount; - - if (string.IsNullOrEmpty(subtitlemode)) throw new ArgumentNullException(nameof(subtitlemode)); - this.SubtitleMode = subtitlemode; - - this.PlayDefaultAudioTrack = playdefaultaudiotrack; - - this.Groups = new HashSet(); - this.Permissions = new HashSet(); - this.ProviderMappings = new HashSet(); - this.Preferences = new HashSet(); - - Init(); - } - - /// - /// Static create function (for use in LINQ queries, etc.) - /// - /// - /// - /// - /// - /// - /// - /// - public static User Create(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack) - { - return new User(username, mustupdatepassword, audiolanguagepreference, authenticationproviderid, invalidloginattemptcount, subtitlemode, playdefaultaudiotrack); - } - /************************************************************************* * Properties *************************************************************************/ /// - /// Identity, Indexed, Required + /// Gets or sets the Id of the user. /// + /// + /// Identity, Indexed, Required. + /// [Key] [Required] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int Id { get; protected set; } + [JsonIgnore] + public Guid Id { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the user's name. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string Username { get; set; } /// - /// Max length = 65535 + /// Gets or sets the user's password, or null if none is set. /// + /// + /// Max length = 65535. + /// [MaxLength(65535)] [StringLength(65535)] public string Password { get; set; } /// - /// Required + /// Gets or sets the user's easy password, or null if none is set. /// + /// + /// Max length = 65535. + /// + [MaxLength(65535)] + [StringLength(65535)] + public string EasyPassword { get; set; } + + /// + /// Gets or sets a value indicating whether the user must update their password. + /// + /// + /// Required. + /// [Required] public bool MustUpdatePassword { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the audio language preference. /// - [Required] + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] public string AudioLanguagePreference { get; set; } /// - /// Required, Max length = 255 + /// Gets or sets the authentication provider id. /// + /// + /// Required, Max length = 255. + /// [Required] [MaxLength(255)] [StringLength(255)] public string AuthenticationProviderId { get; set; } /// - /// Max length = 65535 + /// Gets or sets the password reset provider id. /// - [MaxLength(65535)] - [StringLength(65535)] - public string GroupedFolders { get; set; } + /// + /// Required, Max length = 255. + /// + [Required] + [MaxLength(255)] + [StringLength(255)] + public string PasswordResetProviderId { get; set; } /// - /// Required + /// Gets or sets the invalid login attempt count. /// + /// + /// Required. + /// [Required] public int InvalidLoginAttemptCount { get; set; } /// - /// Max length = 65535 + /// Gets or sets the last activity date. /// - [MaxLength(65535)] - [StringLength(65535)] - public string LatestItemExcludes { get; set; } + public DateTime? LastActivityDate { get; set; } + /// + /// Gets or sets the last login date. + /// + public DateTime? LastLoginDate { get; set; } + + /// + /// Gets or sets the number of login attempts the user can make before they are locked out. + /// public int? LoginAttemptsBeforeLockout { get; set; } /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string MyMediaExcludes { get; set; } - - /// - /// Max length = 65535 - /// - [MaxLength(65535)] - [StringLength(65535)] - public string OrderedViews { get; set; } - - /// - /// Required, Max length = 255 + /// Gets or sets the subtitle mode. /// + /// + /// Required. + /// [Required] - [MaxLength(255)] - [StringLength(255)] - public string SubtitleMode { get; set; } + public SubtitlePlaybackMode SubtitleMode { get; set; } /// - /// Required + /// Gets or sets a value indicating whether the default audio track should be played. /// + /// + /// Required. + /// [Required] public bool PlayDefaultAudioTrack { get; set; } /// - /// Max length = 255 + /// Gets or sets the subtitle language preference. /// + /// + /// Max length = 255. + /// [MaxLength(255)] [StringLength(255)] - public string SubtitleLanguagePrefernce { get; set; } - - public bool? DisplayMissingEpisodes { get; set; } - - public bool? DisplayCollectionsView { get; set; } - - public bool? HidePlayedInLatest { get; set; } - - public bool? RememberAudioSelections { get; set; } - - public bool? RememberSubtitleSelections { get; set; } - - public bool? EnableNextEpisodeAutoPlay { get; set; } - - public bool? EnableUserPreferenceAccess { get; set; } + public string SubtitleLanguagePreference { get; set; } /// - /// Required, ConcurrenyToken + /// Gets or sets a value indicating whether missing episodes should be displayed. /// + /// + /// Required. + /// + [Required] + public bool DisplayMissingEpisodes { get; set; } + + /// + /// Gets or sets a value indicating whether to display the collections view. + /// + /// + /// Required. + /// + [Required] + public bool DisplayCollectionsView { get; set; } + + /// + /// Gets or sets a value indicating whether the user has a local password. + /// + /// + /// Required. + /// + [Required] + public bool EnableLocalPassword { get; set; } + + /// + /// Gets or sets a value indicating whether the server should hide played content in "Latest". + /// + /// + /// Required. + /// + [Required] + public bool HidePlayedInLatest { get; set; } + + /// + /// Gets or sets a value indicating whether to remember audio selections on played content. + /// + /// + /// Required. + /// + [Required] + public bool RememberAudioSelections { get; set; } + + /// + /// Gets or sets a value indicating whether to remember subtitle selections on played content. + /// + /// + /// Required. + /// + [Required] + public bool RememberSubtitleSelections { get; set; } + + /// + /// Gets or sets a value indicating whether to enable auto-play for the next episode. + /// + /// + /// Required. + /// + [Required] + public bool EnableNextEpisodeAutoPlay { get; set; } + + /// + /// Gets or sets a value indicating whether the user should auto-login. + /// + /// + /// Required. + /// + [Required] + public bool EnableAutoLogin { get; set; } + + /// + /// Gets or sets a value indicating whether the user can change their preferences. + /// + /// + /// Required. + /// + [Required] + public bool EnableUserPreferenceAccess { get; set; } + + /// + /// Gets or sets the maximum parental age rating. + /// + public int? MaxParentalAgeRating { get; set; } + + /// + /// Gets or sets the remote client bitrate limit. + /// + public int? RemoteClientBitrateLimit { get; set; } + + /// + /// Gets or sets the internal id. + /// This is a temporary stopgap for until the library db is migrated. + /// This corresponds to the value of the index of this user in the library db. + /// + [Required] + public long InternalId { get; set; } + + /// + /// Gets or sets the user's profile image. Can be null. + /// + // [ForeignKey("UserId")] + public virtual ImageInfo ProfileImage { get; set; } + + [Required] + public SyncPlayAccess SyncPlayAccess { get; set; } + + /// + /// Gets or sets the row version. + /// + /// + /// Required, Concurrency Token. + /// [ConcurrencyCheck] [Required] public uint RowVersion { get; set; } + /************************************************************************* + * Navigation properties + *************************************************************************/ + + /// + /// Gets or sets the list of access schedules this user has. + /// + public virtual ICollection AccessSchedules { get; protected set; } + + /* + /// + /// Gets or sets the list of groups this user is a member of. + /// + [ForeignKey("Group_Groups_Guid")] + public virtual ICollection Groups { get; protected set; } + */ + + /// + /// Gets or sets the list of permissions this user has. + /// + [ForeignKey("Permission_Permissions_Guid")] + public virtual ICollection Permissions { get; protected set; } + + /* + /// + /// Gets or sets the list of provider mappings this user has. + /// + [ForeignKey("ProviderMapping_ProviderMappings_Id")] + public virtual ICollection ProviderMappings { get; protected set; } + */ + + /// + /// Gets or sets the list of preferences this user has. + /// + [ForeignKey("Preference_Preferences_Guid")] + public virtual ICollection Preferences { get; protected set; } + + /// + /// Static create function (for use in LINQ queries, etc.) + /// + /// The username for the created user. + /// The Id of the user's authentication provider. + /// The Id of the user's password reset provider. + /// The created instance. + public static User Create(string username, string authenticationProviderId, string passwordResetProviderId) + { + return new User(username, authenticationProviderId, passwordResetProviderId); + } + + /// public void OnSavingChanges() { RowVersion++; } - /************************************************************************* - * Navigation properties - *************************************************************************/ - [ForeignKey("Group_Groups_Id")] - public virtual ICollection Groups { get; protected set; } + /// + /// Checks whether the user has the specified permission. + /// + /// The permission kind. + /// True if the user has the specified permission. + public bool HasPermission(PermissionKind kind) + { + return Permissions.First(p => p.Kind == kind).Value; + } - [ForeignKey("Permission_Permissions_Id")] - public virtual ICollection Permissions { get; protected set; } + /// + /// Sets the given permission kind to the provided value. + /// + /// The permission kind. + /// The value to set. + public void SetPermission(PermissionKind kind, bool value) + { + Permissions.First(p => p.Kind == kind).Value = value; + } - [ForeignKey("ProviderMapping_ProviderMappings_Id")] - public virtual ICollection ProviderMappings { get; protected set; } + /// + /// Gets the user's preferences for the given preference kind. + /// + /// The preference kind. + /// A string array containing the user's preferences. + public string[] GetPreference(PreferenceKind preference) + { + var val = Preferences.First(p => p.Kind == preference).Value; - [ForeignKey("Preference_Preferences_Id")] - public virtual ICollection Preferences { get; protected set; } + return Equals(val, string.Empty) ? Array.Empty() : val.Split(Delimiter); + } + /// + /// Sets the specified preference to the given value. + /// + /// The preference kind. + /// The values. + public void SetPreference(PreferenceKind preference, string[] values) + { + Preferences.First(p => p.Kind == preference).Value + = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values); + } + + /// + /// Checks whether this user is currently allowed to use the server. + /// + /// True if the current time is within an access schedule, or there are no access schedules. + public bool IsParentalScheduleAllowed() + { + return AccessSchedules.Count == 0 + || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow)); + } + + /// + /// Checks whether the provided folder is in this user's grouped folders. + /// + /// The Guid of the folder. + /// True if the folder is in the user's grouped folders. + public bool IsFolderGrouped(Guid id) + { + return GetPreference(PreferenceKind.GroupedFolders).Any(i => new Guid(i) == id); + } + + private static bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) + { + var localTime = date.ToLocalTime(); + var hour = localTime.TimeOfDay.TotalHours; + + return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) + && hour >= schedule.StartHour + && hour <= schedule.EndHour; + } + + // TODO: make these user configurable? + private void AddDefaultPermissions() + { + Permissions.Add(new Permission(PermissionKind.IsAdministrator, false)); + Permissions.Add(new Permission(PermissionKind.IsDisabled, false)); + Permissions.Add(new Permission(PermissionKind.IsHidden, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllChannels, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true)); + Permissions.Add(new Permission(PermissionKind.EnableAllFolders, true)); + Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false)); + Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true)); + Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true)); + Permissions.Add(new Permission(PermissionKind.EnableMediaPlayback, true)); + Permissions.Add(new Permission(PermissionKind.EnablePlaybackRemuxing, true)); + Permissions.Add(new Permission(PermissionKind.EnablePublicSharing, true)); + Permissions.Add(new Permission(PermissionKind.EnableRemoteAccess, true)); + Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true)); + Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true)); + Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true)); + Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true)); + Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false)); + Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); + } + + private void AddDefaultPreferences() + { + foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast()) + { + Preferences.Add(new Preference(val, string.Empty)); + } + } + + partial void Init(); } } - diff --git a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs similarity index 87% rename from MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs rename to Jellyfin.Data/Enums/DynamicDayOfWeek.cs index 71b16cfba..a33cd9d1c 100644 --- a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs +++ b/Jellyfin.Data/Enums/DynamicDayOfWeek.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -namespace MediaBrowser.Model.Configuration +namespace Jellyfin.Data.Enums { public enum DynamicDayOfWeek { diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index 1506471e8..7d5200874 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -1,26 +1,113 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user permissions. + /// public enum PermissionKind { - IsAdministrator, - IsHidden, - IsDisabled, - BlockUnrateditems, - EnbleSharedDeviceControl, - EnableRemoteAccess, - EnableLiveTvManagement, - EnableLiveTvAccess, - EnableMediaPlayback, - EnableAudioPlaybackTranscoding, - EnableVideoPlaybackTranscoding, - EnableContentDeletion, - EnableContentDownloading, - EnableSyncTranscoding, - EnableMediaConversion, - EnableAllDevices, - EnableAllChannels, - EnableAllFolders, - EnablePublicSharing, - AccessSchedules + /// + /// Whether the user is an administrator. + /// + IsAdministrator = 0, + + /// + /// Whether the user is hidden. + /// + IsHidden = 1, + + /// + /// Whether the user is disabled. + /// + IsDisabled = 2, + + /// + /// Whether the user can control shared devices. + /// + EnableSharedDeviceControl = 3, + + /// + /// Whether the user can access the server remotely. + /// + EnableRemoteAccess = 4, + + /// + /// Whether the user can manage live tv. + /// + EnableLiveTvManagement = 5, + + /// + /// Whether the user can access live tv. + /// + EnableLiveTvAccess = 6, + + /// + /// Whether the user can play media. + /// + EnableMediaPlayback = 7, + + /// + /// Whether the server should transcode audio for the user if requested. + /// + EnableAudioPlaybackTranscoding = 8, + + /// + /// Whether the server should transcode video for the user if requested. + /// + EnableVideoPlaybackTranscoding = 9, + + /// + /// Whether the user can delete content. + /// + EnableContentDeletion = 10, + + /// + /// Whether the user can download content. + /// + EnableContentDownloading = 11, + + /// + /// Whether to enable sync transcoding for the user. + /// + EnableSyncTranscoding = 12, + + /// + /// Whether the user can do media conversion. + /// + EnableMediaConversion = 13, + + /// + /// Whether the user has access to all devices. + /// + EnableAllDevices = 14, + + /// + /// Whether the user has access to all channels. + /// + EnableAllChannels = 15, + + /// + /// Whether the user has access to all folders. + /// + EnableAllFolders = 16, + + /// + /// Whether to enable public sharing for the user. + /// + EnablePublicSharing = 17, + + /// + /// Whether the user can remotely control other users. + /// + EnableRemoteControlOfOtherUsers = 18, + + /// + /// Whether the user is permitted to do playback remuxing. + /// + EnablePlaybackRemuxing = 19, + + /// + /// Whether the server should force transcoding on remote connections for the user. + /// + ForceRemoteSourceTranscoding = 20 } } diff --git a/Jellyfin.Data/Enums/PreferenceKind.cs b/Jellyfin.Data/Enums/PreferenceKind.cs index cd2cb791a..de8eecc73 100644 --- a/Jellyfin.Data/Enums/PreferenceKind.cs +++ b/Jellyfin.Data/Enums/PreferenceKind.cs @@ -1,13 +1,68 @@ namespace Jellyfin.Data.Enums { + /// + /// The types of user preferences. + /// public enum PreferenceKind { - MaxParentalRating, - BlockedTags, - RemoteClientBitrateLimit, - EnabledDevices, - EnabledChannels, - EnabledFolders, - EnableContentDeletionFromFolders + /// + /// A list of blocked tags. + /// + BlockedTags = 0, + + /// + /// A list of blocked channels. + /// + BlockedChannels = 1, + + /// + /// A list of blocked media folders. + /// + BlockedMediaFolders = 2, + + /// + /// A list of enabled devices. + /// + EnabledDevices = 3, + + /// + /// A list of enabled channels + /// + EnabledChannels = 4, + + /// + /// A list of enabled folders. + /// + EnabledFolders = 5, + + /// + /// A list of folders to allow content deletion from. + /// + EnableContentDeletionFromFolders = 6, + + /// + /// A list of latest items to exclude. + /// + LatestItemExcludes = 7, + + /// + /// A list of media to exclude. + /// + MyMediaExcludes = 8, + + /// + /// A list of grouped folders. + /// + GroupedFolders = 9, + + /// + /// A list of unrated items to block. + /// + BlockUnratedItems = 10, + + /// + /// A list of ordered views. + /// + OrderedViews = 11 } } diff --git a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs similarity index 67% rename from MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs rename to Jellyfin.Data/Enums/SubtitlePlaybackMode.cs index f0aa2b98c..c8fc21159 100644 --- a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs +++ b/Jellyfin.Data/Enums/SubtitlePlaybackMode.cs @@ -1,6 +1,6 @@ -#pragma warning disable CS1591 +#pragma warning disable CS1591 -namespace MediaBrowser.Model.Configuration +namespace Jellyfin.Data.Enums { public enum SubtitlePlaybackMode { diff --git a/MediaBrowser.Model/Configuration/SyncplayAccess.cs b/Jellyfin.Data/Enums/SyncPlayAccess.cs similarity index 79% rename from MediaBrowser.Model/Configuration/SyncplayAccess.cs rename to Jellyfin.Data/Enums/SyncPlayAccess.cs index d891a8167..8c13b37a1 100644 --- a/MediaBrowser.Model/Configuration/SyncplayAccess.cs +++ b/Jellyfin.Data/Enums/SyncPlayAccess.cs @@ -1,4 +1,4 @@ -namespace MediaBrowser.Model.Configuration +namespace Jellyfin.Data.Enums { /// /// Enum SyncPlayAccess. @@ -8,16 +8,16 @@ namespace MediaBrowser.Model.Configuration /// /// User can create groups and join them. /// - CreateAndJoinGroups, + CreateAndJoinGroups = 0, /// /// User can only join already existing groups. /// - JoinGroups, + JoinGroups = 1, /// /// SyncPlay is disabled for the user. /// - None + None = 2 } } diff --git a/MediaBrowser.Model/Configuration/UnratedItem.cs b/Jellyfin.Data/Enums/UnratedItem.cs similarity index 84% rename from MediaBrowser.Model/Configuration/UnratedItem.cs rename to Jellyfin.Data/Enums/UnratedItem.cs index e1d1a363d..5259e7739 100644 --- a/MediaBrowser.Model/Configuration/UnratedItem.cs +++ b/Jellyfin.Data/Enums/UnratedItem.cs @@ -1,6 +1,6 @@ #pragma warning disable CS1591 -namespace MediaBrowser.Model.Configuration +namespace Jellyfin.Data.Enums { public enum UnratedItem { diff --git a/Jellyfin.Data/Enums/Weekday.cs b/Jellyfin.Data/Enums/Weekday.cs deleted file mode 100644 index b80a03a33..000000000 --- a/Jellyfin.Data/Enums/Weekday.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Jellyfin.Data.Enums -{ - public enum Weekday - { - Sunday, - Monday, - Tuesday, - Wednesday, - Thursday, - Friday, - Saturday - } -} diff --git a/Jellyfin.Data/IHasPermissions.cs b/Jellyfin.Data/IHasPermissions.cs new file mode 100644 index 000000000..3be72259a --- /dev/null +++ b/Jellyfin.Data/IHasPermissions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; + +namespace Jellyfin.Data +{ + /// + /// An abstraction representing an entity that has permissions. + /// + public interface IHasPermissions + { + /// + /// Gets a collection containing this entity's permissions. + /// + ICollection Permissions { get; } + + /// + /// Checks whether this entity has the specified permission kind. + /// + /// The kind of permission. + /// true if this entity has the specified permission, false otherwise. + bool HasPermission(PermissionKind kind); + + /// + /// Sets the specified permission to the provided value. + /// + /// The kind of permission. + /// The value to set. + void SetPermission(PermissionKind kind, bool value); + } +} diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 58d1ba2f3..282ea511c 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -21,6 +21,7 @@ + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index fd533f8ff..dcac1b34b 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -21,8 +21,6 @@ - - diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs index ec09a619f..f574ebc66 100644 --- a/Jellyfin.Server.Implementations/JellyfinDb.cs +++ b/Jellyfin.Server.Implementations/JellyfinDb.cs @@ -1,9 +1,4 @@ #pragma warning disable CS1591 -#pragma warning disable SA1201 // Constuctors should not follow properties -#pragma warning disable SA1516 // Elements should be followed by a blank line -#pragma warning disable SA1623 // Property's documentation should begin with gets or sets -#pragma warning disable SA1629 // Documentation should end with a period -#pragma warning disable SA1648 // Inheritdoc should be used with inheriting class using System.Linq; using Jellyfin.Data; @@ -15,7 +10,30 @@ namespace Jellyfin.Server.Implementations /// public partial class JellyfinDb : DbContext { + /// + /// Initializes a new instance of the class. + /// + /// The database context options. + public JellyfinDb(DbContextOptions options) : base(options) + { + } + + /// + /// Gets or sets the default connection string. + /// + public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; + + public virtual DbSet AccessSchedules { get; set; } + public virtual DbSet ActivityLogs { get; set; } + + public virtual DbSet ImageInfos { get; set; } + + public virtual DbSet Permissions { get; set; } + + public virtual DbSet Preferences { get; set; } + + public virtual DbSet Users { get; set; } /*public virtual DbSet Artwork { get; set; } public virtual DbSet Books { get; set; } public virtual DbSet BookMetadata { get; set; } @@ -42,12 +60,10 @@ namespace Jellyfin.Server.Implementations public virtual DbSet MovieMetadata { get; set; } public virtual DbSet MusicAlbums { get; set; } public virtual DbSet MusicAlbumMetadata { get; set; } - public virtual DbSet Permissions { get; set; } public virtual DbSet People { get; set; } public virtual DbSet PersonRoles { get; set; } public virtual DbSet Photo { get; set; } public virtual DbSet PhotoMetadata { get; set; } - public virtual DbSet Preferences { get; set; } public virtual DbSet ProviderMappings { get; set; } public virtual DbSet Ratings { get; set; } @@ -62,20 +78,21 @@ namespace Jellyfin.Server.Implementations public virtual DbSet Series { get; set; } public virtual DbSet SeriesMetadata { get; set; } public virtual DbSet Tracks { get; set; } - public virtual DbSet TrackMetadata { get; set; } - public virtual DbSet Users { get; set; } */ + public virtual DbSet TrackMetadata { get; set; }*/ - /// - /// Gets or sets the default connection string. - /// - public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db"; - - /// - public JellyfinDb(DbContextOptions options) : base(options) + /// + public override int SaveChanges() { - } + foreach (var saveEntity in ChangeTracker.Entries() + .Where(e => e.State == EntityState.Modified) + .Select(entry => entry.Entity) + .OfType()) + { + saveEntity.OnSavingChanges(); + } - partial void CustomInit(DbContextOptionsBuilder optionsBuilder); + return base.SaveChanges(); + } /// protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -83,9 +100,6 @@ namespace Jellyfin.Server.Implementations CustomInit(optionsBuilder); } - partial void OnModelCreatingImpl(ModelBuilder modelBuilder); - partial void OnModelCreatedImpl(ModelBuilder modelBuilder); - /// protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -105,16 +119,10 @@ namespace Jellyfin.Server.Implementations OnModelCreatedImpl(modelBuilder); } - public override int SaveChanges() - { - foreach (var saveEntity in ChangeTracker.Entries() - .Where(e => e.State == EntityState.Modified) - .OfType()) - { - saveEntity.OnSavingChanges(); - } + partial void CustomInit(DbContextOptionsBuilder optionsBuilder); - return base.SaveChanges(); - } + partial void OnModelCreatingImpl(ModelBuilder modelBuilder); + + partial void OnModelCreatedImpl(ModelBuilder modelBuilder); } } diff --git a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs index eab531d38..8f5c19900 100644 --- a/Jellyfin.Server.Implementations/JellyfinDbProvider.cs +++ b/Jellyfin.Server.Implementations/JellyfinDbProvider.cs @@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations public JellyfinDbProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - serviceProvider.GetService().Database.Migrate(); + serviceProvider.GetRequiredService().Database.Migrate(); } /// diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs new file mode 100644 index 000000000..6342ce9cf --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs @@ -0,0 +1,312 @@ +#pragma warning disable CS1591 + +// +using System; +using Jellyfin.Server.Implementations; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Jellyfin.Server.Implementations.Migrations +{ + [DbContext(typeof(JellyfinDb))] + [Migration("20200613202153_AddUsers")] + partial class AddUsers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("jellyfin") + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("LogSeverity") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Overview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ShortOverview") + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("Type") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(256); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ActivityLogs"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs new file mode 100644 index 000000000..7e5a76850 --- /dev/null +++ b/Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs @@ -0,0 +1,197 @@ +#pragma warning disable CS1591 +#pragma warning disable SA1601 + +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Jellyfin.Server.Implementations.Migrations +{ + public partial class AddUsers : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false), + Username = table.Column(maxLength: 255, nullable: false), + Password = table.Column(maxLength: 65535, nullable: true), + EasyPassword = table.Column(maxLength: 65535, nullable: true), + MustUpdatePassword = table.Column(nullable: false), + AudioLanguagePreference = table.Column(maxLength: 255, nullable: true), + AuthenticationProviderId = table.Column(maxLength: 255, nullable: false), + PasswordResetProviderId = table.Column(maxLength: 255, nullable: false), + InvalidLoginAttemptCount = table.Column(nullable: false), + LastActivityDate = table.Column(nullable: true), + LastLoginDate = table.Column(nullable: true), + LoginAttemptsBeforeLockout = table.Column(nullable: true), + SubtitleMode = table.Column(nullable: false), + PlayDefaultAudioTrack = table.Column(nullable: false), + SubtitleLanguagePreference = table.Column(maxLength: 255, nullable: true), + DisplayMissingEpisodes = table.Column(nullable: false), + DisplayCollectionsView = table.Column(nullable: false), + EnableLocalPassword = table.Column(nullable: false), + HidePlayedInLatest = table.Column(nullable: false), + RememberAudioSelections = table.Column(nullable: false), + RememberSubtitleSelections = table.Column(nullable: false), + EnableNextEpisodeAutoPlay = table.Column(nullable: false), + EnableAutoLogin = table.Column(nullable: false), + EnableUserPreferenceAccess = table.Column(nullable: false), + MaxParentalAgeRating = table.Column(nullable: true), + RemoteClientBitrateLimit = table.Column(nullable: true), + InternalId = table.Column(nullable: false), + SyncPlayAccess = table.Column(nullable: false), + RowVersion = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AccessSchedules", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: false), + DayOfWeek = table.Column(nullable: false), + StartHour = table.Column(nullable: false), + EndHour = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AccessSchedules", x => x.Id); + table.ForeignKey( + name: "FK_AccessSchedules_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ImageInfos", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + Path = table.Column(maxLength: 512, nullable: false), + LastModified = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ImageInfos", x => x.Id); + table.ForeignKey( + name: "FK_ImageInfos_Users_UserId", + column: x => x.UserId, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Permissions", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(nullable: false), + RowVersion = table.Column(nullable: false), + Permission_Permissions_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Permissions", x => x.Id); + table.ForeignKey( + name: "FK_Permissions_Users_Permission_Permissions_Guid", + column: x => x.Permission_Permissions_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Preferences", + schema: "jellyfin", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Kind = table.Column(nullable: false), + Value = table.Column(maxLength: 65535, nullable: false), + RowVersion = table.Column(nullable: false), + Preference_Preferences_Guid = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Preferences", x => x.Id); + table.ForeignKey( + name: "FK_Preferences_Users_Preference_Preferences_Guid", + column: x => x.Preference_Preferences_Guid, + principalSchema: "jellyfin", + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_AccessSchedules_UserId", + schema: "jellyfin", + table: "AccessSchedules", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_ImageInfos_UserId", + schema: "jellyfin", + table: "ImageInfos", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Permissions_Permission_Permissions_Guid", + schema: "jellyfin", + table: "Permissions", + column: "Permission_Permissions_Guid"); + + migrationBuilder.CreateIndex( + name: "IX_Preferences_Preference_Preferences_Guid", + schema: "jellyfin", + table: "Preferences", + column: "Preference_Preferences_Guid"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AccessSchedules", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "ImageInfos", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Permissions", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Preferences", + schema: "jellyfin"); + + migrationBuilder.DropTable( + name: "Users", + schema: "jellyfin"); + } + } +} diff --git a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs index 1e7ffd235..51fad8224 100644 --- a/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs +++ b/Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs @@ -1,7 +1,9 @@ // using System; +using Jellyfin.Server.Implementations; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace Jellyfin.Server.Implementations.Migrations { @@ -13,7 +15,32 @@ namespace Jellyfin.Server.Implementations.Migrations #pragma warning disable 612, 618 modelBuilder .HasDefaultSchema("jellyfin") - .HasAnnotation("ProductVersion", "3.1.3"); + .HasAnnotation("ProductVersion", "3.1.4"); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("EndHour") + .HasColumnType("REAL"); + + b.Property("StartHour") + .HasColumnType("REAL"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AccessSchedules"); + }); modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b => { @@ -60,6 +87,221 @@ namespace Jellyfin.Server.Implementations.Migrations b.ToTable("ActivityLogs"); }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(512); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("ImageInfos"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Permission_Permissions_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Permission_Permissions_Guid"); + + b.ToTable("Permissions"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Kind") + .HasColumnType("INTEGER"); + + b.Property("Preference_Preferences_Guid") + .HasColumnType("TEXT"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.HasKey("Id"); + + b.HasIndex("Preference_Preferences_Guid"); + + b.ToTable("Preferences"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AudioLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("AuthenticationProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("DisplayCollectionsView") + .HasColumnType("INTEGER"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EasyPassword") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("EnableAutoLogin") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalPassword") + .HasColumnType("INTEGER"); + + b.Property("EnableNextEpisodeAutoPlay") + .HasColumnType("INTEGER"); + + b.Property("EnableUserPreferenceAccess") + .HasColumnType("INTEGER"); + + b.Property("HidePlayedInLatest") + .HasColumnType("INTEGER"); + + b.Property("InternalId") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("LoginAttemptsBeforeLockout") + .HasColumnType("INTEGER"); + + b.Property("MaxParentalAgeRating") + .HasColumnType("INTEGER"); + + b.Property("MustUpdatePassword") + .HasColumnType("INTEGER"); + + b.Property("Password") + .HasColumnType("TEXT") + .HasMaxLength(65535); + + b.Property("PasswordResetProviderId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("RememberAudioSelections") + .HasColumnType("INTEGER"); + + b.Property("RememberSubtitleSelections") + .HasColumnType("INTEGER"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.Property("SubtitleMode") + .HasColumnType("INTEGER"); + + b.Property("SyncPlayAccess") + .HasColumnType("INTEGER"); + + b.Property("Username") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(255); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("AccessSchedules") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithOne("ProfileImage") + .HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Permissions") + .HasForeignKey("Permission_Permissions_Guid"); + }); + + modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b => + { + b.HasOne("Jellyfin.Data.Entities.User", null) + .WithMany("Preferences") + .HasForeignKey("Preference_Preferences_Guid"); + }); #pragma warning restore 612, 618 } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs similarity index 92% rename from Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs rename to Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs index 02f150607..162dc6f5e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs @@ -1,14 +1,16 @@ +#nullable enable + using System; using System.Linq; using System.Text; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Cryptography; -namespace Emby.Server.Implementations.Library +namespace Jellyfin.Server.Implementations.Users { /// /// The default authentication provider. @@ -47,7 +49,7 @@ namespace Emby.Server.Implementations.Library { if (resolvedUser == null) { - throw new AuthenticationException($"Specified user does not exist."); + throw new AuthenticationException("Specified user does not exist."); } bool success = false; @@ -61,7 +63,7 @@ namespace Emby.Server.Implementations.Library }); } - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + byte[] passwordBytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = PasswordHash.Parse(resolvedUser.Password); if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) @@ -69,7 +71,7 @@ namespace Emby.Server.Implementations.Library { byte[] calculatedHash = _cryptographyProvider.ComputeHash( readyHash.Id, - passwordbytes, + passwordBytes, readyHash.Salt.ToArray()); if (readyHash.Hash.SequenceEqual(calculatedHash)) @@ -95,7 +97,7 @@ namespace Emby.Server.Implementations.Library /// public bool HasPassword(User user) - => !string.IsNullOrEmpty(user.Password); + => !string.IsNullOrEmpty(user?.Password); /// public Task ChangePassword(User user, string newPassword) @@ -129,7 +131,7 @@ namespace Emby.Server.Implementations.Library } /// - public string GetEasyPasswordHash(User user) + public string? GetEasyPasswordHash(User user) { return string.IsNullOrEmpty(user.EasyPassword) ? null diff --git a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs similarity index 75% rename from Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs rename to Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs index 6c6fbd86f..cf5a01f08 100644 --- a/Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs +++ b/Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs @@ -1,8 +1,11 @@ +#nullable enable + using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Configuration; @@ -10,7 +13,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; -namespace Emby.Server.Implementations.Library +namespace Jellyfin.Server.Implementations.Users { /// /// The default password reset provider. @@ -51,54 +54,49 @@ namespace Emby.Server.Implementations.Library /// public async Task RedeemPasswordResetPin(string pin) { - SerializablePasswordReset spr; - List usersreset = new List(); - foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) + var usersReset = new List(); + foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*")) { - using (var str = File.OpenRead(resetfile)) + SerializablePasswordReset spr; + await using (var str = File.OpenRead(resetFile)) { spr = await _jsonSerializer.DeserializeFromStreamAsync(str).ConfigureAwait(false); } - if (spr.ExpirationDate < DateTime.Now) + if (spr.ExpirationDate < DateTime.UtcNow) { - File.Delete(resetfile); + File.Delete(resetFile); } else if (string.Equals( spr.Pin.Replace("-", string.Empty, StringComparison.Ordinal), pin.Replace("-", string.Empty, StringComparison.Ordinal), StringComparison.InvariantCultureIgnoreCase)) { - var resetUser = _userManager.GetUserByName(spr.UserName); - if (resetUser == null) - { - throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); - } + var resetUser = _userManager.GetUserByName(spr.UserName) + ?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found"); await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false); - usersreset.Add(resetUser.Name); - File.Delete(resetfile); + usersReset.Add(resetUser.Username); + File.Delete(resetFile); } } - if (usersreset.Count < 1) + if (usersReset.Count < 1) { throw new ResourceNotFoundException($"No Users found with a password reset request matching pin {pin}"); } - else + + return new PinRedeemResult { - return new PinRedeemResult - { - Success = true, - UsersReset = usersreset.ToArray() - }; - } + Success = true, + UsersReset = usersReset.ToArray() + }; } /// - public async Task StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork) + public async Task StartForgotPasswordProcess(User user, bool isInNetwork) { - string pin = string.Empty; + string pin; using (var cryptoRandom = RandomNumberGenerator.Create()) { byte[] bytes = new byte[4]; @@ -106,30 +104,33 @@ namespace Emby.Server.Implementations.Library pin = BitConverter.ToString(bytes); } - DateTime expireTime = DateTime.Now.AddMinutes(30); - string filePath = _passwordResetFileBase + user.InternalId + ".json"; + DateTime expireTime = DateTime.UtcNow.AddMinutes(30); + string filePath = _passwordResetFileBase + user.Id + ".json"; SerializablePasswordReset spr = new SerializablePasswordReset { ExpirationDate = expireTime, Pin = pin, PinFile = filePath, - UserName = user.Name + UserName = user.Username }; - using (FileStream fileStream = File.OpenWrite(filePath)) + await using (FileStream fileStream = File.OpenWrite(filePath)) { _jsonSerializer.SerializeToStream(spr, fileStream); await fileStream.FlushAsync().ConfigureAwait(false); } + user.EasyPassword = pin; + await _userManager.UpdateUserAsync(user).ConfigureAwait(false); + return new ForgotPasswordResult { Action = ForgotPasswordAction.PinCode, PinExpirationDate = expireTime, - PinFile = filePath }; } +#nullable disable private class SerializablePasswordReset : PasswordPinCreationResult { public string Pin { get; set; } diff --git a/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs new file mode 100644 index 000000000..140853e52 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs @@ -0,0 +1,67 @@ +#nullable enable +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Events; + +namespace Jellyfin.Server.Implementations.Users +{ + public sealed class DeviceAccessEntryPoint : IServerEntryPoint + { + private readonly IUserManager _userManager; + private readonly IAuthenticationRepository _authRepo; + private readonly IDeviceManager _deviceManager; + private readonly ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.OnUserUpdated += OnUserUpdated; + + return Task.CompletedTask; + } + + public void Dispose() + { + } + + private void OnUserUpdated(object? sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.HasPermission(PermissionKind.EnableAllDevices)) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs similarity index 85% rename from Emby.Server.Implementations/Library/InvalidAuthProvider.cs rename to Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs index dc61aacd7..491aba1d4 100644 --- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs +++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs @@ -1,8 +1,10 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; +#nullable enable -namespace Emby.Server.Implementations.Library +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Authentication; + +namespace Jellyfin.Server.Implementations.Users { /// /// An invalid authentication provider. @@ -39,12 +41,6 @@ namespace Emby.Server.Implementations.Library // Nothing here } - /// - public string GetPasswordHash(User user) - { - return string.Empty; - } - /// public string GetEasyPasswordHash(User user) { diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs new file mode 100644 index 000000000..904114758 --- /dev/null +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -0,0 +1,841 @@ +#nullable enable +#pragma warning disable CA1307 + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using MediaBrowser.Common; +using MediaBrowser.Common.Cryptography; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Server.Implementations.Users +{ + /// + /// Manages the creation and retrieval of instances. + /// + public class UserManager : IUserManager + { + private readonly JellyfinDbProvider _dbProvider; + private readonly ICryptoProvider _cryptoProvider; + private readonly INetworkManager _networkManager; + private readonly IApplicationHost _appHost; + private readonly IImageProcessor _imageProcessor; + private readonly ILogger _logger; + + private IAuthenticationProvider[] _authenticationProviders = null!; + private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!; + private InvalidAuthProvider _invalidAuthProvider = null!; + private IPasswordResetProvider[] _passwordResetProviders = null!; + private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!; + + /// + /// Initializes a new instance of the class. + /// + /// The database provider. + /// The cryptography provider. + /// The network manager. + /// The application host. + /// The image processor. + /// The logger. + public UserManager( + JellyfinDbProvider dbProvider, + ICryptoProvider cryptoProvider, + INetworkManager networkManager, + IApplicationHost appHost, + IImageProcessor imageProcessor, + ILogger logger) + { + _dbProvider = dbProvider; + _cryptoProvider = cryptoProvider; + _networkManager = networkManager; + _appHost = appHost; + _imageProcessor = imageProcessor; + _logger = logger; + } + + /// + public event EventHandler>? OnUserPasswordChanged; + + /// + public event EventHandler>? OnUserUpdated; + + /// + public event EventHandler>? OnUserCreated; + + /// + public event EventHandler>? OnUserDeleted; + + /// + public event EventHandler>? OnUserLockedOut; + + /// + public IEnumerable Users => _dbProvider.CreateContext().Users; + + /// + public IEnumerable UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id); + + /// + public User? GetUserById(Guid id) + { + if (id == Guid.Empty) + { + throw new ArgumentException("Guid can't be empty", nameof(id)); + } + + return _dbProvider.CreateContext().Users.Find(id); + } + + /// + public User? GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Invalid username", nameof(name)); + } + + // This can't use an overload with StringComparer because that would cause the query to + // have to be evaluated client-side. + return _dbProvider.CreateContext().Users.FirstOrDefault(u => string.Equals(u.Username, name)); + } + + /// + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrWhiteSpace(newName)) + { + throw new ArgumentException("Invalid username", nameof(newName)); + } + + if (user.Username.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "A user with the name '{0}' already exists.", + newName)); + } + + user.Username = newName; + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserUpdated?.Invoke(this, new GenericEventArgs(user)); + } + + /// + public void UpdateUser(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + dbContext.SaveChanges(); + } + + /// + public async Task UpdateUserAsync(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Users.Update(user); + + await dbContext.SaveChangesAsync().ConfigureAwait(false); + } + + /// + public User CreateUser(string name) + { + if (!IsValidUsername(name)) + { + throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); + } + + var dbContext = _dbProvider.CreateContext(); + + // TODO: Remove after user item data is migrated. + var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0; + + var newUser = new User( + name, + _defaultAuthenticationProvider.GetType().FullName, + _defaultPasswordResetProvider.GetType().FullName) + { + InternalId = max + 1 + }; + dbContext.Users.Add(newUser); + dbContext.SaveChanges(); + + OnUserCreated?.Invoke(this, new GenericEventArgs(newUser)); + + return newUser; + } + + /// + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var dbContext = _dbProvider.CreateContext(); + + if (dbContext.Users.Find(user.Id) == null) + { + throw new ArgumentException(string.Format( + CultureInfo.InvariantCulture, + "The user cannot be deleted because there is no user with the Name {0} and Id {1}.", + user.Username, + user.Id)); + } + + if (dbContext.Users.Count() == 1) + { + throw new InvalidOperationException(string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one user in the system.", + user.Username)); + } + + if (user.HasPermission(PermissionKind.IsAdministrator) + && Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) + { + throw new ArgumentException( + string.Format( + CultureInfo.InvariantCulture, + "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", + user.Username), + nameof(user)); + } + + dbContext.Users.Remove(user); + dbContext.SaveChanges(); + OnUserDeleted?.Invoke(this, new GenericEventArgs(user)); + } + + /// + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + /// + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + /// + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + await UpdateUserAsync(user).ConfigureAwait(false); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1) + { + GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1); + UpdateUser(user); + + OnUserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + public UserDto GetUserDto(User user, string? remoteEndPoint = null) + { + var hasPassword = GetAuthenticationProvider(user).HasPassword(user); + return new UserDto + { + Name = user.Username, + Id = user.Id, + ServerId = _appHost.SystemId, + HasPassword = hasPassword, + HasConfiguredPassword = hasPassword, + HasConfiguredEasyPassword = !string.IsNullOrEmpty(user.EasyPassword), + EnableAutoLogin = user.EnableAutoLogin, + LastLoginDate = user.LastLoginDate, + LastActivityDate = user.LastActivityDate, + PrimaryImageTag = user.ProfileImage != null ? _imageProcessor.GetImageCacheTag(user) : null, + Configuration = new UserConfiguration + { + SubtitleMode = user.SubtitleMode, + HidePlayedInLatest = user.HidePlayedInLatest, + EnableLocalPassword = user.EnableLocalPassword, + PlayDefaultAudioTrack = user.PlayDefaultAudioTrack, + DisplayCollectionsView = user.DisplayCollectionsView, + DisplayMissingEpisodes = user.DisplayMissingEpisodes, + AudioLanguagePreference = user.AudioLanguagePreference, + RememberAudioSelections = user.RememberAudioSelections, + EnableNextEpisodeAutoPlay = user.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = user.RememberSubtitleSelections, + SubtitleLanguagePreference = user.SubtitleLanguagePreference ?? string.Empty, + OrderedViews = user.GetPreference(PreferenceKind.OrderedViews), + GroupedFolders = user.GetPreference(PreferenceKind.GroupedFolders), + MyMediaExcludes = user.GetPreference(PreferenceKind.MyMediaExcludes), + LatestItemsExcludes = user.GetPreference(PreferenceKind.LatestItemExcludes) + }, + Policy = new UserPolicy + { + MaxParentalRating = user.MaxParentalAgeRating, + EnableUserPreferenceAccess = user.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = user.RemoteClientBitrateLimit ?? 0, + AuthenticationProviderId = user.AuthenticationProviderId, + PasswordResetProviderId = user.PasswordResetProviderId, + InvalidLoginAttemptCount = user.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout ?? -1, + IsAdministrator = user.HasPermission(PermissionKind.IsAdministrator), + IsHidden = user.HasPermission(PermissionKind.IsHidden), + IsDisabled = user.HasPermission(PermissionKind.IsDisabled), + EnableSharedDeviceControl = user.HasPermission(PermissionKind.EnableSharedDeviceControl), + EnableRemoteAccess = user.HasPermission(PermissionKind.EnableRemoteAccess), + EnableLiveTvManagement = user.HasPermission(PermissionKind.EnableLiveTvManagement), + EnableLiveTvAccess = user.HasPermission(PermissionKind.EnableLiveTvAccess), + EnableMediaPlayback = user.HasPermission(PermissionKind.EnableMediaPlayback), + EnableAudioPlaybackTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding), + EnableVideoPlaybackTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + EnableContentDeletion = user.HasPermission(PermissionKind.EnableContentDeletion), + EnableContentDownloading = user.HasPermission(PermissionKind.EnableContentDownloading), + EnableSyncTranscoding = user.HasPermission(PermissionKind.EnableSyncTranscoding), + EnableMediaConversion = user.HasPermission(PermissionKind.EnableMediaConversion), + EnableAllChannels = user.HasPermission(PermissionKind.EnableAllChannels), + EnableAllDevices = user.HasPermission(PermissionKind.EnableAllDevices), + EnableAllFolders = user.HasPermission(PermissionKind.EnableAllFolders), + EnableRemoteControlOfOtherUsers = user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers), + EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), + EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + AccessSchedules = user.AccessSchedules.ToArray(), + BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), + EnabledChannels = user.GetPreference(PreferenceKind.EnabledChannels), + EnabledDevices = user.GetPreference(PreferenceKind.EnabledDevices), + EnabledFolders = user.GetPreference(PreferenceKind.EnabledFolders), + EnableContentDeletionFromFolders = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders), + SyncPlayAccess = user.SyncPlayAccess, + BlockedChannels = user.GetPreference(PreferenceKind.BlockedChannels), + BlockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders), + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems).Select(Enum.Parse).ToArray() + } + }; + } + + /// + public async Task AuthenticateUser( + string username, + string password, + string passwordSha1, + string remoteEndPoint, + bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint); + throw new ArgumentNullException(nameof(username)); + } + + var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + bool success; + IAuthenticationProvider? authenticationProvider; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + success = authResult.success; + } + else + { + var authResult = await AuthenticateLocalUser(username, password, null, remoteEndPoint) + .ConfigureAwait(false); + authenticationProvider = authResult.authenticationProvider; + string updatedUsername = authResult.username; + success = authResult.success; + + if (success + && authenticationProvider != null + && !(authenticationProvider is DefaultAuthenticationProvider)) + { + // Trust the username returned by the authentication provider + username = updatedUsername; + + // Search the database for the user again + // the authentication provider might have created it + user = Users + .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase)); + + if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy) + { + UpdatePolicy(user.Id, hasNewUserPolicy.GetNewUserPolicy()); + + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = authenticationProvider.GetType().FullName; + + if (!string.Equals(providerId, user.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.AuthenticationProviderId = providerId; + await UpdateUserAsync(user).ConfigureAwait(false); + } + } + + if (user == null) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + username, + remoteEndPoint); + throw new AuthenticationException("Invalid username or password entered."); + } + + if (user.HasPermission(PermissionKind.IsDisabled)) + { + _logger.LogInformation( + "Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException( + $"The {user.Username} account is currently disabled. Please consult with your administrator."); + } + + if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && + !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + _logger.LogInformation( + "Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + _logger.LogInformation( + "Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", + username, + remoteEndPoint); + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + } + + user.InvalidLoginAttemptCount = 0; + await UpdateUserAsync(user).ConfigureAwait(false); + _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username); + } + else + { + IncrementInvalidLoginAttemptCount(user); + _logger.LogInformation( + "Authentication request for {UserName} has been denied (IP: {IP}).", + user.Username, + remoteEndPoint); + } + + return success ? user : null; + } + + /// + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + var user = string.IsNullOrWhiteSpace(enteredUsername) ? null : GetUserByName(enteredUsername); + + if (user != null && isInNetwork) + { + var passwordResetProvider = GetPasswordResetProvider(user); + return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false); + } + + return new ForgotPasswordResult + { + Action = ForgotPasswordAction.InNetworkRequired, + PinFile = string.Empty + }; + } + + /// + public async Task RedeemPasswordResetPin(string pin) + { + foreach (var provider in _passwordResetProviders) + { + var result = await provider.RedeemPasswordResetPin(pin).ConfigureAwait(false); + + if (result.Success) + { + return result; + } + } + + return new PinRedeemResult + { + Success = false, + UsersReset = Array.Empty() + }; + } + + /// + public void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + _passwordResetProviders = passwordResetProviders.ToArray(); + + _invalidAuthProvider = _authenticationProviders.OfType().First(); + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + _defaultPasswordResetProvider = _passwordResetProviders.OfType().First(); + } + + /// + public void Initialize() + { + // TODO: Refactor the startup wizard so that it doesn't require a user to already exist. + var dbContext = _dbProvider.CreateContext(); + + if (dbContext.Users.Any()) + { + return; + } + + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + + _logger.LogWarning("No users, creating one with username {UserName}", defaultName); + + if (!IsValidUsername(defaultName)) + { + throw new ArgumentException("Provided username is not valid!", defaultName); + } + + var newUser = CreateUser(defaultName); + newUser.SetPermission(PermissionKind.IsAdministrator, true); + newUser.SetPermission(PermissionKind.EnableContentDeletion, true); + newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true); + + dbContext.Users.Update(newUser); + dbContext.SaveChanges(); + } + + /// + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + /// + public NameIdPair[] GetPasswordResetProviders() + { + return _passwordResetProviders + .Where(provider => provider.IsEnabled) + .OrderBy(i => i is DefaultPasswordResetProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = i.GetType().FullName + }) + .ToArray(); + } + + /// + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); + user.SubtitleMode = config.SubtitleMode; + user.HidePlayedInLatest = config.HidePlayedInLatest; + user.EnableLocalPassword = config.EnableLocalPassword; + user.PlayDefaultAudioTrack = config.PlayDefaultAudioTrack; + user.DisplayCollectionsView = config.DisplayCollectionsView; + user.DisplayMissingEpisodes = config.DisplayMissingEpisodes; + user.AudioLanguagePreference = config.AudioLanguagePreference; + user.RememberAudioSelections = config.RememberAudioSelections; + user.EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay; + user.RememberSubtitleSelections = config.RememberSubtitleSelections; + user.SubtitleLanguagePreference = config.SubtitleLanguagePreference; + + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + dbContext.Update(user); + dbContext.SaveChanges(); + } + + /// + public void UpdatePolicy(Guid userId, UserPolicy policy) + { + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!"); + + // The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0" + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; + + user.MaxParentalAgeRating = policy.MaxParentalRating; + user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess; + user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit; + user.AuthenticationProviderId = policy.AuthenticationProviderId; + user.PasswordResetProviderId = policy.PasswordResetProviderId; + user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount; + user.LoginAttemptsBeforeLockout = maxLoginAttempts; + user.SyncPlayAccess = policy.SyncPlayAccess; + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + + user.AccessSchedules.Clear(); + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + // TODO: fix this at some point + user.SetPreference( + PreferenceKind.BlockUnratedItems, + policy.BlockUnratedItems?.Select(i => i.ToString()).ToArray() ?? Array.Empty()); + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + + dbContext.Update(user); + dbContext.SaveChanges(); + } + + /// + public void ClearProfileImage(User user) + { + var dbContext = _dbProvider.CreateContext(); + dbContext.Remove(user.ProfileImage); + dbContext.SaveChanges(); + } + + private static bool IsValidUsername(string name) + { + // This is some regex that matches only on unicode "word" characters, as well as -, _ and @ + // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.) + return Regex.IsMatch(name, @"^[\w\-'._@]*$"); + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user)[0]; + } + + private IPasswordResetProvider GetPasswordResetProvider(User user) + { + return GetPasswordResetProviders(user)[0]; + } + + private IList GetAuthenticationProviders(User? user) + { + var authenticationProviderId = user?.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToList(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)).ToList(); + } + + if (providers.Count == 0) + { + // Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found + _logger.LogWarning( + "User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected", + user?.Username, + user?.AuthenticationProviderId); + providers = new List + { + _invalidAuthProvider + }; + } + + return providers; + } + + private IList GetPasswordResetProviders(User user) + { + var passwordResetProviderId = user?.PasswordResetProviderId; + var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(passwordResetProviderId)) + { + providers = providers.Where(i => + string.Equals(passwordResetProviderId, i.GetType().FullName, StringComparison.OrdinalIgnoreCase)) + .ToArray(); + } + + if (providers.Length == 0) + { + providers = new IPasswordResetProvider[] + { + _defaultPasswordResetProvider + }; + } + + return providers; + } + + private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser( + string username, + string password, + User? user, + string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider? authenticationProvider = null; + + foreach (var provider in GetAuthenticationProviders(user)) + { + var providerAuthResult = + await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + var updatedUsername = providerAuthResult.username; + success = providerAuthResult.success; + + if (success) + { + authenticationProvider = provider; + username = updatedUsername; + break; + } + } + + if (!success + && _networkManager.IsInLocalNetwork(remoteEndPoint) + && user?.EnableLocalPassword == true + && !string.IsNullOrEmpty(user.EasyPassword)) + { + // Check easy password + var passwordHash = PasswordHash.Parse(user.EasyPassword); + var hash = _cryptoProvider.ComputeHash( + passwordHash.Id, + Encoding.UTF8.GetBytes(password), + passwordHash.Salt.ToArray()); + success = passwordHash.Hash.SequenceEqual(hash); + } + + return (authenticationProvider, username, success); + } + + private async Task<(string username, bool success)> AuthenticateWithProvider( + IAuthenticationProvider provider, + string username, + string password, + User? resolvedUser) + { + try + { + var authenticationResult = provider is IRequiresResolvedUser requiresResolvedUser + ? await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false) + : await provider.Authenticate(username, password).ConfigureAwait(false); + + if (authenticationResult.Username != username) + { + _logger.LogDebug("Authentication provider provided updated username {1}", authenticationResult.Username); + username = authenticationResult.Username; + } + + return (username, true); + } + catch (AuthenticationException ex) + { + _logger.LogError(ex, "Error authenticating with provider {Provider}", provider.Name); + + return (username, false); + } + } + + private void IncrementInvalidLoginAttemptCount(User user) + { + user.InvalidLoginAttemptCount++; + int? maxInvalidLogins = user.LoginAttemptsBeforeLockout; + if (maxInvalidLogins.HasValue && user.InvalidLoginAttemptCount >= maxInvalidLogins) + { + user.SetPermission(PermissionKind.IsDisabled, true); + OnUserLockedOut?.Invoke(this, new GenericEventArgs(user)); + _logger.LogWarning( + "Disabling user {Username} due to {Attempts} unsuccessful login attempts.", + user.Username, + user.InvalidLoginAttemptCount); + } + + UpdateUser(user); + } + } +} diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 331a32c73..fe07411a6 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -7,8 +7,10 @@ using Emby.Server.Implementations; using Jellyfin.Drawing.Skia; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Activity; +using Jellyfin.Server.Implementations.Users; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Activity; using MediaBrowser.Model.IO; using Microsoft.EntityFrameworkCore; @@ -63,12 +65,15 @@ namespace Jellyfin.Server // TODO: Set up scoping and use AddDbContextPool serviceCollection.AddDbContext( - options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"), - ServiceLifetime.Transient); + options => options + .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}") + .UseLazyLoadingProxies(), + ServiceLifetime.Transient); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); base.RegisterServices(serviceCollection); } @@ -79,7 +84,11 @@ namespace Jellyfin.Server /// protected override IEnumerable GetAssembliesWithPartsInternal() { + // Jellyfin.Server yield return typeof(CoreAppHost).Assembly; + + // Jellyfin.Server.Implementations + yield return typeof(JellyfinDb).Assembly; } /// diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs index 473f62737..c98263223 100644 --- a/Jellyfin.Server/Migrations/MigrationRunner.cs +++ b/Jellyfin.Server/Migrations/MigrationRunner.cs @@ -19,7 +19,8 @@ namespace Jellyfin.Server.Migrations typeof(Routines.DisableTranscodingThrottling), typeof(Routines.CreateUserLoggingConfigFile), typeof(Routines.MigrateActivityLogDb), - typeof(Routines.RemoveDuplicateExtras) + typeof(Routines.RemoveDuplicateExtras), + typeof(Routines.MigrateUserDb) }; /// diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs new file mode 100644 index 000000000..2be10c708 --- /dev/null +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -0,0 +1,208 @@ +using System; +using System.IO; +using Emby.Server.Implementations.Data; +using Emby.Server.Implementations.Serialization; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; +using Jellyfin.Server.Implementations; +using Jellyfin.Server.Implementations.Users; +using MediaBrowser.Common.Json; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace Jellyfin.Server.Migrations.Routines +{ + /// + /// The migration routine for migrating the user database to EF Core. + /// + public class MigrateUserDb : IMigrationRoutine + { + private const string DbFilename = "users.db"; + + private readonly ILogger _logger; + private readonly IServerApplicationPaths _paths; + private readonly JellyfinDbProvider _provider; + private readonly MyXmlSerializer _xmlSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The server application paths. + /// The database provider. + /// The xml serializer. + public MigrateUserDb( + ILogger logger, + IServerApplicationPaths paths, + JellyfinDbProvider provider, + MyXmlSerializer xmlSerializer) + { + _logger = logger; + _paths = paths; + _provider = provider; + _xmlSerializer = xmlSerializer; + } + + /// + public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C"); + + /// + public string Name => "MigrateUserDatabase"; + + /// + public void Perform() + { + var dataPath = _paths.DataPath; + _logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin."); + + using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null)) + { + var dbContext = _provider.CreateContext(); + + var queryResult = connection.Query("SELECT * FROM LocalUsersv2"); + + dbContext.RemoveRange(dbContext.Users); + dbContext.SaveChanges(); + + foreach (var entry in queryResult) + { + UserMockup mockup = JsonSerializer.Deserialize(entry[2].ToBlob(), JsonDefaults.GetOptions()); + var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name); + + var config = File.Exists(Path.Combine(userDataDir, "config.xml")) + ? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml")) + : new UserConfiguration(); + var policy = File.Exists(Path.Combine(userDataDir, "policy.xml")) + ? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml")) + : new UserPolicy(); + policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace( + "Emby.Server.Implementations.Library", + "Jellyfin.Server.Implementations.Users", + StringComparison.Ordinal) + ?? typeof(DefaultAuthenticationProvider).FullName; + + policy.PasswordResetProviderId = typeof(DefaultPasswordResetProvider).FullName; + int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch + { + -1 => null, + 0 => 3, + _ => policy.LoginAttemptsBeforeLockout + }; + + var user = new User(mockup.Name, policy.AuthenticationProviderId, policy.PasswordResetProviderId) + { + Id = entry[1].ReadGuidFromBlob(), + InternalId = entry[0].ToInt64(), + MaxParentalAgeRating = policy.MaxParentalRating, + EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess, + RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit, + InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount, + LoginAttemptsBeforeLockout = maxLoginAttempts, + SubtitleMode = config.SubtitleMode, + HidePlayedInLatest = config.HidePlayedInLatest, + EnableLocalPassword = config.EnableLocalPassword, + PlayDefaultAudioTrack = config.PlayDefaultAudioTrack, + DisplayCollectionsView = config.DisplayCollectionsView, + DisplayMissingEpisodes = config.DisplayMissingEpisodes, + AudioLanguagePreference = config.AudioLanguagePreference, + RememberAudioSelections = config.RememberAudioSelections, + EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay, + RememberSubtitleSelections = config.RememberSubtitleSelections, + SubtitleLanguagePreference = config.SubtitleLanguagePreference, + Password = mockup.Password, + EasyPassword = mockup.EasyPassword, + LastLoginDate = mockup.LastLoginDate, + LastActivityDate = mockup.LastActivityDate + }; + + if (mockup.ImageInfos.Length > 0) + { + ItemImageInfo info = mockup.ImageInfos[0]; + + user.ProfileImage = new ImageInfo(info.Path) + { + LastModified = info.DateModified + }; + } + + user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator); + user.SetPermission(PermissionKind.IsHidden, policy.IsHidden); + user.SetPermission(PermissionKind.IsDisabled, policy.IsDisabled); + user.SetPermission(PermissionKind.EnableSharedDeviceControl, policy.EnableSharedDeviceControl); + user.SetPermission(PermissionKind.EnableRemoteAccess, policy.EnableRemoteAccess); + user.SetPermission(PermissionKind.EnableLiveTvManagement, policy.EnableLiveTvManagement); + user.SetPermission(PermissionKind.EnableLiveTvAccess, policy.EnableLiveTvAccess); + user.SetPermission(PermissionKind.EnableMediaPlayback, policy.EnableMediaPlayback); + user.SetPermission(PermissionKind.EnableAudioPlaybackTranscoding, policy.EnableAudioPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableVideoPlaybackTranscoding, policy.EnableVideoPlaybackTranscoding); + user.SetPermission(PermissionKind.EnableContentDeletion, policy.EnableContentDeletion); + user.SetPermission(PermissionKind.EnableContentDownloading, policy.EnableContentDownloading); + user.SetPermission(PermissionKind.EnableSyncTranscoding, policy.EnableSyncTranscoding); + user.SetPermission(PermissionKind.EnableMediaConversion, policy.EnableMediaConversion); + user.SetPermission(PermissionKind.EnableAllChannels, policy.EnableAllChannels); + user.SetPermission(PermissionKind.EnableAllDevices, policy.EnableAllDevices); + user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); + user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); + user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); + user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + + foreach (var policyAccessSchedule in policy.AccessSchedules) + { + user.AccessSchedules.Add(policyAccessSchedule); + } + + user.SetPreference(PreferenceKind.BlockedTags, policy.BlockedTags); + user.SetPreference(PreferenceKind.EnabledChannels, policy.EnabledChannels); + user.SetPreference(PreferenceKind.EnabledDevices, policy.EnabledDevices); + user.SetPreference(PreferenceKind.EnabledFolders, policy.EnabledFolders); + user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders); + user.SetPreference(PreferenceKind.OrderedViews, config.OrderedViews); + user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders); + user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes); + user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes); + + dbContext.Users.Add(user); + } + + dbContext.SaveChanges(); + } + + try + { + File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old")); + + var journalPath = Path.Combine(dataPath, DbFilename + "-journal"); + if (File.Exists(journalPath)) + { + File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal")); + } + } + catch (IOException e) + { + _logger.LogError(e, "Error renaming legacy user database to 'users.db.old'"); + } + } + +#nullable disable + internal class UserMockup + { + public string Password { get; set; } + + public string EasyPassword { get; set; } + + public DateTime? LastLoginDate { get; set; } + + public DateTime? LastActivityDate { get; set; } + + public string Name { get; set; } + + public ItemImageInfo[] ImageInfos { get; set; } + } + } +} diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 2cd68ac1b..a91a9b580 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -94,8 +95,8 @@ namespace MediaBrowser.Api var authenticatedUser = auth.User; // If they're going to update the record of another user, they must be an administrator - if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator) - || (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess)) + if ((!userId.Equals(auth.UserId) && !authenticatedUser.HasPermission(PermissionKind.IsAdministrator)) + || (restrictUserPreferences && !authenticatedUser.EnableUserPreferenceAccess)) { throw new SecurityException("Unauthorized access."); } @@ -267,7 +268,6 @@ namespace MediaBrowser.Api Name = name.Replace(BaseItem.SlugChar, '&'), IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery @@ -275,7 +275,6 @@ namespace MediaBrowser.Api Name = name.Replace(BaseItem.SlugChar, '/'), IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); result ??= libraryManager.GetItemList(new InternalItemsQuery @@ -283,7 +282,6 @@ namespace MediaBrowser.Api Name = name.Replace(BaseItem.SlugChar, '?'), IncludeItemTypes = new[] { typeof(T).Name }, DtoOptions = dtoOptions - }).OfType().FirstOrDefault(); return result; diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs index fd9b8c396..3cab9fb66 100644 --- a/MediaBrowser.Api/ChannelService.cs +++ b/MediaBrowser.Api/ChannelService.cs @@ -240,7 +240,6 @@ namespace MediaBrowser.Api { Fields = request.GetItemFields() } - }; foreach (var filter in request.GetFilters()) diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 316be04a0..3ad51de8d 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -17,7 +17,6 @@ namespace MediaBrowser.Api [Authenticated] public class GetConfiguration : IReturn { - } [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] @@ -51,7 +50,6 @@ namespace MediaBrowser.Api [Authenticated(Roles = "Admin")] public class GetDefaultMetadataOptions : IReturn { - } [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index dd3f3e738..18860983e 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -92,7 +92,6 @@ namespace MediaBrowser.Api.Devices var sessions = _authRepo.Get(new AuthenticationInfoQuery { DeviceId = request.Id - }).Items; foreach (var session in sessions) diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 82d471412..fddf78465 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -100,7 +100,6 @@ namespace MediaBrowser.Api [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")] public class GetDefaultDirectoryBrowser : IReturn { - } /// diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 5eb72cdb1..833a684a5 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -149,7 +150,6 @@ namespace MediaBrowser.Api { Name = i.Item1.Name, Id = i.Item1.Id - }).ToArray(); } else @@ -158,7 +158,6 @@ namespace MediaBrowser.Api { Name = i.Item1.Name, Id = i.Item1.Id - }).ToArray(); } diff --git a/MediaBrowser.Api/IHasItemFields.cs b/MediaBrowser.Api/IHasItemFields.cs index 85b4a7e2d..6359de77d 100644 --- a/MediaBrowser.Api/IHasItemFields.cs +++ b/MediaBrowser.Api/IHasItemFields.cs @@ -43,7 +43,6 @@ namespace MediaBrowser.Api } return null; - }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index fdb15d96a..6f2956c5d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -18,9 +18,11 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; +using User = Jellyfin.Data.Entities.User; namespace MediaBrowser.Api.Images { @@ -408,14 +410,14 @@ namespace MediaBrowser.Api.Images { var item = _userManager.GetUserById(request.Id); - return GetImage(request, Guid.Empty, item, false); + return GetImage(request, item, false); } public object Head(GetUserImage request) { var item = _userManager.GetUserById(request.Id); - return GetImage(request, Guid.Empty, item, true); + return GetImage(request, item, true); } public object Get(GetItemByNameImage request) @@ -448,9 +450,9 @@ namespace MediaBrowser.Api.Images request.Type = Enum.Parse(GetPathValue(3).ToString(), true); - var item = _userManager.GetUserById(id); + var user = _userManager.GetUserById(id); - return PostImage(item, request.RequestStream, request.Type, Request.ContentType); + return PostImage(user, request.RequestStream, Request.ContentType); } /// @@ -477,9 +479,17 @@ namespace MediaBrowser.Api.Images var userId = request.Id; AssertCanUpdateUser(_authContext, _userManager, userId, true); - var item = _userManager.GetUserById(userId); + var user = _userManager.GetUserById(userId); + try + { + File.Delete(user.ProfileImage.Path); + } + catch (IOException e) + { + Logger.LogError(e, "Error deleting user profile image:"); + } - item.DeleteImage(request.Type, request.Index ?? 0); + _userManager.ClearProfileImage(user); } /// @@ -567,14 +577,14 @@ namespace MediaBrowser.Api.Images throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type)); } - bool cropwhitespace; + bool cropWhitespace; if (request.CropWhitespace.HasValue) { - cropwhitespace = request.CropWhitespace.Value; + cropWhitespace = request.CropWhitespace.Value; } else { - cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; + cropWhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; } var outputFormats = GetOutputFormats(request); @@ -597,13 +607,90 @@ namespace MediaBrowser.Api.Images itemId, request, imageInfo, - cropwhitespace, + cropWhitespace, outputFormats, cacheDuration, responseHeaders, isHeadRequest); } + public Task GetImage(ImageRequest request, User user, bool isHeadRequest) + { + var imageInfo = GetImageInfo(request, user); + + TimeSpan? cacheDuration = null; + + if (!string.IsNullOrEmpty(request.Tag)) + { + cacheDuration = TimeSpan.FromDays(365); + } + + var responseHeaders = new Dictionary + { + {"transferMode.dlna.org", "Interactive"}, + {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"} + }; + + var outputFormats = GetOutputFormats(request); + + return GetImageResult(user.Id, + request, + imageInfo, + outputFormats, + cacheDuration, + responseHeaders, + isHeadRequest); + } + + private async Task GetImageResult( + Guid itemId, + ImageRequest request, + ItemImageInfo info, + IReadOnlyCollection supportedFormats, + TimeSpan? cacheDuration, + IDictionary headers, + bool isHeadRequest) + { + info.Type = ImageType.Profile; + var options = new ImageProcessingOptions + { + CropWhiteSpace = true, + Height = request.Height, + ImageIndex = request.Index ?? 0, + Image = info, + Item = null, // Hack alert + ItemId = itemId, + MaxHeight = request.MaxHeight, + MaxWidth = request.MaxWidth, + Quality = request.Quality ?? 100, + Width = request.Width, + AddPlayedIndicator = request.AddPlayedIndicator, + PercentPlayed = 0, + UnplayedCount = request.UnplayedCount, + Blur = request.Blur, + BackgroundColor = request.BackgroundColor, + ForegroundLayer = request.ForegroundLayer, + SupportedOutputFormats = supportedFormats + }; + + var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); + + headers[HeaderNames.Vary] = HeaderNames.Accept; + + return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions + { + CacheDuration = cacheDuration, + ResponseHeaders = headers, + ContentType = imageResult.Item2, + DateLastModified = imageResult.Item3, + IsHeadRequest = isHeadRequest, + Path = imageResult.Item1, + + FileShare = FileShare.Read + + }).ConfigureAwait(false); + } + private async Task GetImageResult( BaseItem item, Guid itemId, @@ -656,7 +743,6 @@ namespace MediaBrowser.Api.Images Path = imageResult.Item1, FileShare = FileShare.Read - }).ConfigureAwait(false); } @@ -741,13 +827,35 @@ namespace MediaBrowser.Api.Images /// The request. /// The item. /// System.String. - private ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) + private static ItemImageInfo GetImageInfo(ImageRequest request, BaseItem item) { var index = request.Index ?? 0; return item.GetImageInfo(request.Type, index); } + private static ItemImageInfo GetImageInfo(ImageRequest request, User user) + { + var info = new ItemImageInfo + { + Path = user.ProfileImage.Path, + Type = ImageType.Primary, + DateModified = user.ProfileImage.LastModified, + }; + + if (request.Width.HasValue) + { + info.Width = request.Width.Value; + } + + if (request.Height.HasValue) + { + info.Height = request.Height.Value; + } + + return info; + } + /// /// Posts the image. /// @@ -758,15 +866,7 @@ namespace MediaBrowser.Api.Images /// Task. public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType) { - using var reader = new StreamReader(inputStream); - var text = await reader.ReadToEndAsync().ConfigureAwait(false); - - var bytes = Convert.FromBase64String(text); - - var memoryStream = new MemoryStream(bytes) - { - Position = 0 - }; + var memoryStream = await GetMemoryStream(inputStream); // Handle image/png; charset=utf-8 mimeType = mimeType.Split(';').FirstOrDefault(); @@ -775,5 +875,32 @@ namespace MediaBrowser.Api.Images entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None); } + + private static async Task GetMemoryStream(Stream inputStream) + { + using var reader = new StreamReader(inputStream); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); + + var bytes = Convert.FromBase64String(text); + return new MemoryStream(bytes) + { + Position = 0 + }; + } + + private async Task PostImage(User user, Stream inputStream, string mimeType) + { + var memoryStream = await GetMemoryStream(inputStream); + + // Handle image/png; charset=utf-8 + mimeType = mimeType.Split(';').FirstOrDefault(); + var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); + user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))); + + await _providerManager + .SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path) + .ConfigureAwait(false); + await _userManager.UpdateUserAsync(user); + } } } diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 358ac30fa..2633a5d3c 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -152,7 +152,6 @@ namespace MediaBrowser.Api.Images IncludeAllLanguages = request.IncludeAllLanguages, IncludeDisabledProviders = true, ImageType = request.Type - }, CancellationToken.None).ConfigureAwait(false); var imagesList = images.ToArray(); diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 68e3dfa59..862411209 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -220,7 +220,7 @@ namespace MediaBrowser.Api { var item = _libraryManager.GetItemById(new Guid(request.Id)); - //foreach (var key in request.ProviderIds) + // foreach (var key in request.ProviderIds) //{ // var value = key.Value; @@ -233,8 +233,8 @@ namespace MediaBrowser.Api // Since the refresh process won't erase provider Ids, we need to set this explicitly now. item.ProviderIds = request.ProviderIds; - //item.ProductionYear = request.ProductionYear; - //item.Name = request.Name; + // item.ProductionYear = request.ProductionYear; + // item.Name = request.Name; return _providerManager.RefreshFullItem( item, diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 9d53a2610..eb64abb4d 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -6,6 +6,7 @@ using System.Net; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Api.Movies; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; @@ -14,7 +15,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; @@ -27,6 +27,12 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Api.Library { @@ -350,6 +356,7 @@ namespace MediaBrowser.Api.Library _moviesServiceLogger = moviesServiceLogger; } + // Content Types available for each Library private string[] GetRepresentativeItemTypes(string contentType) { return contentType switch @@ -359,7 +366,7 @@ namespace MediaBrowser.Api.Library CollectionType.Movies => new[] {"Movie"}, CollectionType.TvShows => new[] {"Series", "Season", "Episode"}, CollectionType.Books => new[] {"Book"}, - CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"}, + CollectionType.Music => new[] {"MusicArtist", "MusicAlbum", "Audio", "MusicVideo"}, CollectionType.HomeVideos => new[] {"Video", "Photo"}, CollectionType.Photos => new[] {"Video", "Photo"}, CollectionType.MusicVideos => new[] {"MusicVideo"}, @@ -425,7 +432,6 @@ namespace MediaBrowser.Api.Library return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); } @@ -556,7 +562,6 @@ namespace MediaBrowser.Api.Library _authContext) { Request = Request, - }.GetSimilarItemsResult(request); } @@ -654,7 +659,6 @@ namespace MediaBrowser.Api.Library { EnableImages = false } - }).Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProvider.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); foreach (var item in series) @@ -683,7 +687,6 @@ namespace MediaBrowser.Api.Library { EnableImages = false } - }); if (!string.IsNullOrWhiteSpace(request.ImdbId)) @@ -763,8 +766,8 @@ namespace MediaBrowser.Api.Library { try { - _activityManager.Create(new Jellyfin.Data.Entities.ActivityLog( - string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Name, item.Name), + _activityManager.Create(new ActivityLog( + string.Format(_localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), "UserDownloadingContent", auth.UserId) { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 5fe4c0cca..b00a5fec8 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -7,6 +7,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Api.UserLibrary; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -678,7 +679,6 @@ namespace MediaBrowser.Api.LiveTv [Authenticated] public class GetTunerHostTypes : IReturn> { - } [Route("/LiveTv/Tuners/Discvover", "GET")] @@ -825,7 +825,6 @@ namespace MediaBrowser.Api.LiveTv { Name = i.Name, Id = i.Id - }).ToList(), Mappings = mappings, @@ -844,7 +843,6 @@ namespace MediaBrowser.Api.LiveTv { Url = "https://json.schedulesdirect.org/20141201/available/countries", BufferContent = false - }).ConfigureAwait(false); return ResultFactory.GetResult(Request, response, "application/json"); @@ -859,7 +857,7 @@ namespace MediaBrowser.Api.LiveTv throw new SecurityException("Anonymous live tv management is not allowed."); } - if (!user.Policy.EnableLiveTvManagement) + if (!user.HasPermission(PermissionKind.EnableLiveTvManagement)) { throw new SecurityException("The current user does not have permission to manage live tv."); } @@ -957,7 +955,6 @@ namespace MediaBrowser.Api.LiveTv SortBy = request.GetOrderBy(), SortOrder = request.SortOrder ?? SortOrder.Ascending, AddCurrentProgram = request.AddCurrentProgram - }, options, CancellationToken.None); var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); @@ -1112,7 +1109,6 @@ namespace MediaBrowser.Api.LiveTv Fields = request.GetItemFields(), ImageTypeLimit = request.ImageTypeLimit, EnableImages = request.EnableImages - }, options); return ToOptimizedResult(result); @@ -1151,7 +1147,6 @@ namespace MediaBrowser.Api.LiveTv SeriesTimerId = request.SeriesTimerId, IsActive = request.IsActive, IsScheduled = request.IsScheduled - }, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); @@ -1187,7 +1182,6 @@ namespace MediaBrowser.Api.LiveTv { SortOrder = request.SortOrder, SortBy = request.SortBy - }, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs index 95a37dfc5..e9629439d 100644 --- a/MediaBrowser.Api/Movies/CollectionService.cs +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -79,7 +79,6 @@ namespace MediaBrowser.Api.Movies ParentId = parentId, ItemIdList = SplitValue(request.Ids, ','), UserIds = new[] { userId } - }); var dtoOptions = GetDtoOptions(_authContext, request); diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index a9c7f5d6f..88ca0aa23 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; @@ -15,6 +15,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; namespace MediaBrowser.Api.Movies { @@ -159,8 +161,8 @@ namespace MediaBrowser.Api.Movies IncludeItemTypes = new[] { typeof(Movie).Name, - //typeof(Trailer).Name, - //typeof(LiveTvProgram).Name + // typeof(Trailer).Name, + // typeof(LiveTvProgram).Name }, // IsMovie = true OrderBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random }.Select(i => new ValueTuple(i, SortOrder.Descending)).ToArray(), @@ -192,7 +194,6 @@ namespace MediaBrowser.Api.Movies ParentId = parentIdGuid, Recursive = true, DtoOptions = dtoOptions - }); var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); @@ -251,7 +252,12 @@ namespace MediaBrowser.Api.Movies return categories.OrderBy(i => i.RecommendationType); } - private IEnumerable GetWithDirector(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetWithDirector( + User user, + IEnumerable names, + int itemLimit, + DtoOptions dtoOptions, + RecommendationType type) { var itemTypes = new List { typeof(Movie).Name }; if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions) @@ -272,7 +278,6 @@ namespace MediaBrowser.Api.Movies IsMovie = true, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) @@ -313,7 +318,6 @@ namespace MediaBrowser.Api.Movies IsMovie = true, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).GroupBy(i => i.GetProviderId(MetadataProvider.Imdb) ?? Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture)) .Select(x => x.First()) .Take(itemLimit) @@ -353,7 +357,6 @@ namespace MediaBrowser.Api.Movies SimilarTo = item, EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }); if (similar.Count > 0) diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 0b5334235..a7758b100 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -82,7 +82,6 @@ namespace MediaBrowser.Api.Movies _authContext) { Request = Request, - }.Get(getItems); } } diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index cacec8d64..ebd3eb64a 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -191,6 +192,5 @@ namespace MediaBrowser.Api.Music return result; } - } } diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 24297d500..2eb6198c0 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; @@ -196,7 +197,7 @@ namespace MediaBrowser.Api.Playback if (state.VideoRequest != null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec)) { var auth = AuthorizationContext.GetAuthorizationInfo(Request); - if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding) + if (auth.User != null && !auth.User.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state); @@ -215,7 +216,7 @@ namespace MediaBrowser.Api.Playback UseShellExecute = false, // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, + // RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, @@ -684,7 +685,7 @@ namespace MediaBrowser.Api.Playback state.User = UserManager.GetUserById(auth.UserId); } - //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || + // if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) //{ @@ -715,9 +716,9 @@ namespace MediaBrowser.Api.Playback state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? + // var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - //if (primaryImage != null) + // if (primaryImage != null) //{ // state.AlbumCoverPath = primaryImage.Path; //} @@ -884,7 +885,7 @@ namespace MediaBrowser.Api.Playback if (transcodingProfile != null) { state.EstimateContentLength = transcodingProfile.EstimateContentLength; - //state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; + // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; if (state.VideoRequest != null) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 627421aac..c2d49a93b 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -178,7 +178,7 @@ namespace MediaBrowser.Api.Playback.Hls var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture); text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase); - //text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase); + // text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase); return text; } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 0a6ed2786..c0dfcf4c1 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -257,7 +257,7 @@ namespace MediaBrowser.Api.Playback.Hls throw; } - //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); + // await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); } else { @@ -277,8 +277,8 @@ namespace MediaBrowser.Api.Playback.Hls } } - //Logger.LogInformation("waiting for {0}", segmentPath); - //while (!File.Exists(segmentPath)) + // Logger.LogInformation("waiting for {0}", segmentPath); + // while (!File.Exists(segmentPath)) //{ // await Task.Delay(50, cancellationToken).ConfigureAwait(false); //} @@ -717,7 +717,7 @@ namespace MediaBrowser.Api.Playback.Hls // Having problems in android return false; - //return state.VideoRequest.VideoBitRate.HasValue; + // return state.VideoRequest.VideoBitRate.HasValue; } /// @@ -972,7 +972,7 @@ namespace MediaBrowser.Api.Playback.Hls var queryStringIndex = Request.RawUrl.IndexOf('?'); var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - //if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1) + // if ((Request.UserAgent ?? string.Empty).IndexOf("roku", StringComparison.OrdinalIgnoreCase) != -1) //{ // queryString = string.Empty; //} @@ -1100,7 +1100,7 @@ namespace MediaBrowser.Api.Playback.Hls } } - //args += " -flags -global_header"; + // args += " -flags -global_header"; } else { @@ -1142,7 +1142,7 @@ namespace MediaBrowser.Api.Playback.Hls args += " " + keyFrameArg + gopArg; } - //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; + // args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -1164,7 +1164,7 @@ namespace MediaBrowser.Api.Playback.Hls args += " -start_at_zero"; } - //args += " -flags -global_header"; + // args += " -flags -global_header"; } if (!string.IsNullOrEmpty(state.OutputVideoSync)) diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs index e2d771ec6..2dc62fda7 100644 --- a/MediaBrowser.Api/Playback/MediaInfoService.cs +++ b/MediaBrowser.Api/Playback/MediaInfoService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; @@ -400,21 +401,24 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - Logger.LogInformation("User policy for {0}. EnableAudioPlaybackTranscoding: {1}", user.Name, user.Policy.EnableAudioPlaybackTranscoding); + Logger.LogInformation( + "User policy for {0}. EnableAudioPlaybackTranscoding: {1}", + user.Username, + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); } else { Logger.LogInformation("User policy for {0}. EnablePlaybackRemuxing: {1} EnableVideoPlaybackTranscoding: {2} EnableAudioPlaybackTranscoding: {3}", - user.Name, - user.Policy.EnablePlaybackRemuxing, - user.Policy.EnableVideoPlaybackTranscoding, - user.Policy.EnableAudioPlaybackTranscoding); + user.Username, + user.HasPermission(PermissionKind.EnablePlaybackRemuxing), + user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding), + user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)); } // Beginning of Playback Determination: Attempt DirectPlay first if (mediaSource.SupportsDirectPlay) { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { mediaSource.SupportsDirectPlay = false; } @@ -428,14 +432,16 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - if (!user.Policy.EnableAudioPlaybackTranscoding) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { options.ForceDirectPlay = true; } } else if (item is Video) { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { options.ForceDirectPlay = true; } @@ -463,7 +469,7 @@ namespace MediaBrowser.Api.Playback if (mediaSource.SupportsDirectStream) { - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { mediaSource.SupportsDirectStream = false; } @@ -473,14 +479,16 @@ namespace MediaBrowser.Api.Playback if (item is Audio) { - if (!user.Policy.EnableAudioPlaybackTranscoding) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { options.ForceDirectStream = true; } } else if (item is Video) { - if (!user.Policy.EnableAudioPlaybackTranscoding && !user.Policy.EnableVideoPlaybackTranscoding && !user.Policy.EnablePlaybackRemuxing) + if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding) + && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing)) { options.ForceDirectStream = true; } @@ -512,7 +520,7 @@ namespace MediaBrowser.Api.Playback ? streamBuilder.BuildAudioItem(options) : streamBuilder.BuildVideoItem(options); - if (mediaSource.IsRemote && user.Policy.ForceRemoteSourceTranscoding) + if (mediaSource.IsRemote && user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding)) { if (streamInfo != null) { @@ -576,10 +584,10 @@ namespace MediaBrowser.Api.Playback } } - private long? GetMaxBitrate(long? clientMaxBitrate, User user) + private long? GetMaxBitrate(long? clientMaxBitrate, Jellyfin.Data.Entities.User user) { var maxBitrate = clientMaxBitrate; - var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0; + var remoteClientMaxBitrate = user?.RemoteClientBitrateLimit ?? 0; if (remoteClientMaxBitrate <= 0) { @@ -638,7 +646,6 @@ namespace MediaBrowser.Api.Playback } return 1; - }).ThenBy(i => { // Let's assume direct streaming a file is just as desirable as direct playing a remote url @@ -648,7 +655,6 @@ namespace MediaBrowser.Api.Playback } return 1; - }).ThenBy(i => { return i.Protocol switch @@ -664,7 +670,6 @@ namespace MediaBrowser.Api.Playback } return 1; - }).ThenBy(originalList.IndexOf) .ToArray(); } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index c7bf055fb..43cde440c 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -231,7 +231,7 @@ namespace MediaBrowser.Api.Playback.Progressive } //// Not static but transcode cache file exists - //if (isTranscodeCached && state.VideoRequest == null) + // if (isTranscodeCached && state.VideoRequest == null) //{ // var contentType = state.GetMimeType(outputPath); diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index a53b848f9..ffc5e1554 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -96,8 +96,8 @@ namespace MediaBrowser.Api.Playback.Progressive bytesRead = await CopyToInternalAsyncWithSyncRead(inputStream, outputStream, cancellationToken).ConfigureAwait(false); } - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); + // var position = fs.Position; + // _logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); if (bytesRead == 0) { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 4de81655c..a35e6c201 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -59,7 +59,6 @@ namespace MediaBrowser.Api.Playback.Progressive [Route("/Videos/{Id}/stream", "HEAD")] public class GetVideoStream : VideoStreamRequest { - } /// diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index a3b319d44..b2d101a5b 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -259,7 +259,6 @@ namespace MediaBrowser.Api.Playback UserId = request.UserId, DeviceProfile = deviceProfile, MediaSourceId = request.MediaSourceId - }).ConfigureAwait(false); var mediaSource = playbackInfoResult.MediaSources[0]; diff --git a/MediaBrowser.Api/PlaylistService.cs b/MediaBrowser.Api/PlaylistService.cs index 953b00e35..d5def03be 100644 --- a/MediaBrowser.Api/PlaylistService.cs +++ b/MediaBrowser.Api/PlaylistService.cs @@ -161,7 +161,6 @@ namespace MediaBrowser.Api ItemIdList = GetGuids(request.Ids), UserId = request.UserId, MediaType = request.MediaType - }).ConfigureAwait(false); return ToOptimizedResult(result); diff --git a/MediaBrowser.Api/PluginService.cs b/MediaBrowser.Api/PluginService.cs index 7f74511ee..fd1075727 100644 --- a/MediaBrowser.Api/PluginService.cs +++ b/MediaBrowser.Api/PluginService.cs @@ -75,7 +75,7 @@ namespace MediaBrowser.Api public Stream RequestStream { get; set; } } - //TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, + // TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins, // delete all these registration endpoints. They are only kept for compatibility. [Route("/Registrations/{Name}", "GET", Summary = "Gets registration status for a feature", IsHidden = true)] [Authenticated] diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index e9d339c6e..4a2f96ed8 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -180,7 +180,6 @@ namespace MediaBrowser.Api IsNews = request.IsNews, IsSeries = request.IsSeries, IsSports = request.IsSports - }); return new SearchHintResult diff --git a/MediaBrowser.Api/Sessions/SessionService.cs b/MediaBrowser.Api/Sessions/SessionService.cs index 020bb5042..d986eea65 100644 --- a/MediaBrowser.Api/Sessions/SessionService.cs +++ b/MediaBrowser.Api/Sessions/SessionService.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Library; @@ -326,12 +327,12 @@ namespace MediaBrowser.Api.Sessions var user = _userManager.GetUserById(request.ControllableByUserId); - if (!user.Policy.EnableRemoteControlOfOtherUsers) + if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) { result = result.Where(i => i.UserId.Equals(Guid.Empty) || i.ContainsUser(request.ControllableByUserId)); } - if (!user.Policy.EnableSharedDeviceControl) + if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) { result = result.Where(i => !i.UserId.Equals(Guid.Empty)); } diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index dcd22280a..90c324ff3 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -218,6 +218,5 @@ namespace MediaBrowser.Api return points; } - } } diff --git a/MediaBrowser.Api/SuggestionsService.cs b/MediaBrowser.Api/SuggestionsService.cs index 91f85db6f..32d3bde5c 100644 --- a/MediaBrowser.Api/SuggestionsService.cs +++ b/MediaBrowser.Api/SuggestionsService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; diff --git a/MediaBrowser.Api/System/SystemService.cs b/MediaBrowser.Api/System/SystemService.cs index c57cc93d5..4f10a4ad2 100644 --- a/MediaBrowser.Api/System/SystemService.cs +++ b/MediaBrowser.Api/System/SystemService.cs @@ -24,20 +24,17 @@ namespace MediaBrowser.Api.System [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)] public class GetSystemInfo : IReturn { - } [Route("/System/Info/Public", "GET", Summary = "Gets public information about the server")] public class GetPublicSystemInfo : IReturn { - } [Route("/System/Ping", "POST")] [Route("/System/Ping", "GET")] public class PingSystem : IReturnVoid { - } /// @@ -83,7 +80,6 @@ namespace MediaBrowser.Api.System [Authenticated] public class GetWakeOnLanInfo : IReturn { - } /// @@ -153,7 +149,6 @@ namespace MediaBrowser.Api.System DateModified = _fileSystem.GetLastWriteTimeUtc(i), Name = i.Name, Size = i.Length - }).OrderByDescending(i => i.DateModified) .ThenByDescending(i => i.DateCreated) .ThenBy(i => i.Name) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index cd8e8dfbe..23062b67b 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -306,7 +306,6 @@ namespace MediaBrowser.Api ParentId = parentIdGuid, Recursive = true, DtoOptions = options - }); var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user); @@ -378,7 +377,7 @@ namespace MediaBrowser.Api { var user = _userManager.GetUserById(request.UserId); - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { @@ -390,7 +389,6 @@ namespace MediaBrowser.Api IsMissing = request.IsMissing, IsSpecialSeason = request.IsSpecialSeason, AdjacentTo = request.AdjacentTo - }); var dtoOptions = GetDtoOptions(_authContext, request); @@ -404,7 +402,7 @@ namespace MediaBrowser.Api }; } - private Series GetSeries(string seriesId, User user) + private Series GetSeries(string seriesId) { if (!string.IsNullOrWhiteSpace(seriesId)) { @@ -433,7 +431,7 @@ namespace MediaBrowser.Api } else if (request.Season.HasValue) { - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { @@ -446,7 +444,7 @@ namespace MediaBrowser.Api } else { - var series = GetSeries(request.Id, user); + var series = GetSeries(request.Id); if (series == null) { diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 559082ff4..4802849f4 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -321,7 +322,6 @@ namespace MediaBrowser.Api.UserLibrary { ibnItems = ibnItems.Take(request.Limit.Value); } - } var tuples = ibnItems.Select(i => new Tuple>(i, new List())); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index f3c0441e1..49d534c36 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -2,10 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dto; @@ -14,6 +15,7 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; namespace MediaBrowser.Api.UserLibrary { @@ -86,7 +88,7 @@ namespace MediaBrowser.Api.UserLibrary var ancestorIds = Array.Empty(); - var excludeFolderIds = user.Configuration.LatestItemsExcludes; + var excludeFolderIds = user.GetPreference(PreferenceKind.LatestItemExcludes); if (parentIdGuid.Equals(Guid.Empty) && excludeFolderIds.Length > 0) { ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) @@ -211,14 +213,14 @@ namespace MediaBrowser.Api.UserLibrary request.IncludeItemTypes = "Playlist"; } - bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id) + bool isInEnabledFolder = user.GetPreference(PreferenceKind.EnabledFolders).Any(i => new Guid(i) == item.Id) // Assume all folders inside an EnabledChannel are enabled - || user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id); + || user.GetPreference(PreferenceKind.EnabledChannels).Any(i => new Guid(i) == item.Id); var collectionFolders = _libraryManager.GetCollectionFolders(item); foreach (var collectionFolder in collectionFolders) { - if (user.Policy.EnabledFolders.Contains( + if (user.GetPreference(PreferenceKind.EnabledFolders).Contains( collectionFolder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { @@ -226,9 +228,12 @@ namespace MediaBrowser.Api.UserLibrary } } - if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels) + if (!(item is UserRootFolder) + && !isInEnabledFolder + && !user.HasPermission(PermissionKind.EnableAllFolders) + && !user.HasPermission(PermissionKind.EnableAllChannels)) { - Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name); + Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name); return new QueryResult { Items = Array.Empty(), diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index d0faca163..ab231626b 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -1,8 +1,8 @@ using System; using System.Globalization; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index 7fa750adb..f75852885 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -312,7 +312,7 @@ namespace MediaBrowser.Api.UserLibrary if (!request.IsPlayed.HasValue) { - if (user.Configuration.HidePlayedInLatest) + if (user.HidePlayedInLatest) { request.IsPlayed = false; } diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 0fffb0622..73d5ec6de 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -129,7 +129,6 @@ namespace MediaBrowser.Api.UserLibrary { Name = i.Name, Id = i.Id.ToString("N", CultureInfo.InvariantCulture) - }) .OrderBy(i => i.Name) .ToArray(); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 78fc6c694..9cb9baf63 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; @@ -300,12 +301,12 @@ namespace MediaBrowser.Api if (request.IsDisabled.HasValue) { - users = users.Where(i => i.Policy.IsDisabled == request.IsDisabled.Value); + users = users.Where(i => i.HasPermission(PermissionKind.IsDisabled) == request.IsDisabled.Value); } if (request.IsHidden.HasValue) { - users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value); + users = users.Where(i => i.HasPermission(PermissionKind.IsHidden) == request.IsHidden.Value); } if (filterByDevice) @@ -322,12 +323,12 @@ namespace MediaBrowser.Api { if (!_networkManager.IsInLocalNetwork(Request.RemoteIp)) { - users = users.Where(i => i.Policy.EnableRemoteAccess); + users = users.Where(i => i.HasPermission(PermissionKind.EnableRemoteAccess)); } } var result = users - .OrderBy(u => u.Name) + .OrderBy(u => u.Username) .Select(i => _userManager.GetUserDto(i, Request.RemoteIp)) .ToArray(); @@ -397,7 +398,7 @@ namespace MediaBrowser.Api // Password should always be null return Post(new AuthenticateUserByName { - Username = user.Name, + Username = user.Username, Password = null, Pw = request.Pw }); @@ -456,7 +457,12 @@ namespace MediaBrowser.Api } else { - var success = await _userManager.AuthenticateUser(user.Name, request.CurrentPw, request.CurrentPassword, Request.RemoteIp, false).ConfigureAwait(false); + var success = await _userManager.AuthenticateUser( + user.Username, + request.CurrentPw, + request.CurrentPassword, + Request.RemoteIp, + false).ConfigureAwait(false); if (success == null) { @@ -506,10 +512,10 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(id); - if (string.Equals(user.Name, dtoUser.Name, StringComparison.Ordinal)) + if (string.Equals(user.Username, dtoUser.Name, StringComparison.Ordinal)) { - _userManager.UpdateUser(user); - _userManager.UpdateConfiguration(user, dtoUser.Configuration); + await _userManager.UpdateUserAsync(user); + _userManager.UpdateConfiguration(user.Id, dtoUser.Configuration); } else { @@ -560,7 +566,6 @@ namespace MediaBrowser.Api AssertCanUpdateUser(_authContext, _userManager, request.Id, false); _userManager.UpdateConfiguration(request.Id, request); - } public void Post(UpdateUserPolicy request) @@ -568,24 +573,24 @@ namespace MediaBrowser.Api var user = _userManager.GetUserById(request.Id); // If removing admin access - if (!request.IsAdministrator && user.Policy.IsAdministrator) + if (!request.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator)) { - if (_userManager.Users.Count(i => i.Policy.IsAdministrator) == 1) + if (_userManager.Users.Count(i => i.HasPermission(PermissionKind.IsAdministrator)) == 1) { throw new ArgumentException("There must be at least one user in the system with administrative access."); } } // If disabling - if (request.IsDisabled && user.Policy.IsAdministrator) + if (request.IsDisabled && user.HasPermission(PermissionKind.IsAdministrator)) { throw new ArgumentException("Administrators cannot be disabled."); } // If disabling - if (request.IsDisabled && !user.Policy.IsDisabled) + if (request.IsDisabled && !user.HasPermission(PermissionKind.IsDisabled)) { - if (_userManager.Users.Count(i => !i.Policy.IsDisabled) == 1) + if (_userManager.Users.Count(i => !i.HasPermission(PermissionKind.IsDisabled)) == 1) { throw new ArgumentException("There must be at least one enabled user in the system."); } @@ -594,7 +599,7 @@ namespace MediaBrowser.Api _sessionMananger.RevokeUserTokens(user.Id, currentToken); } - _userManager.UpdateUserPolicy(request.Id, request); + _userManager.UpdatePolicy(request.Id, request); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs new file mode 100644 index 000000000..0a36e1cb2 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverter.cs @@ -0,0 +1,82 @@ +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// Converter for Dictionaries without string key. + /// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys. + /// + /// Type of key. + /// Type of value. + internal sealed class JsonNonStringKeyDictionaryConverter : JsonConverter> + { + /// + /// Read JSON. + /// + /// The Utf8JsonReader. + /// The type to convert. + /// The json serializer options. + /// Typed dictionary. + /// Not supported. + public override IDictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var convertedType = typeof(Dictionary<,>).MakeGenericType(typeof(string), typeToConvert.GenericTypeArguments[1]); + var value = JsonSerializer.Deserialize(ref reader, convertedType, options); + var instance = (Dictionary)Activator.CreateInstance( + typeToConvert, + BindingFlags.Instance | BindingFlags.Public, + null, + null, + CultureInfo.CurrentCulture); + var enumerator = (IEnumerator)convertedType.GetMethod("GetEnumerator")!.Invoke(value, null); + var parse = typeof(TKey).GetMethod( + "Parse", + 0, + BindingFlags.Public | BindingFlags.Static, + null, + CallingConventions.Any, + new[] { typeof(string) }, + null); + if (parse == null) + { + throw new NotSupportedException($"{typeof(TKey)} as TKey in IDictionary is not supported."); + } + + while (enumerator.MoveNext()) + { + var element = (KeyValuePair)enumerator.Current; + instance.Add((TKey)parse.Invoke(null, new[] { (object?)element.Key }), element.Value); + } + + return instance; + } + + /// + /// Write dictionary as Json. + /// + /// The Utf8JsonWriter. + /// The dictionary value. + /// The Json serializer options. + public override void Write(Utf8JsonWriter writer, IDictionary value, JsonSerializerOptions options) + { + var convertedDictionary = new Dictionary(value.Count); + foreach (var (k, v) in value) + { + if (k != null) + { + convertedDictionary[k.ToString()] = v; + } + } + + JsonSerializer.Serialize(writer, convertedDictionary, options); + } + } +} diff --git a/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs new file mode 100644 index 000000000..52f360740 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonNonStringKeyDictionaryConverterFactory.cs @@ -0,0 +1,59 @@ +#nullable enable + +using System; +using System.Collections; +using System.Globalization; +using System.Reflection; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// + /// https://github.com/dotnet/runtime/issues/30524#issuecomment-524619972. + /// TODO This can be removed when System.Text.Json supports Dictionaries with non-string keys. + /// + internal sealed class JsonNonStringKeyDictionaryConverterFactory : JsonConverterFactory + { + /// + /// Only convert objects that implement IDictionary and do not have string keys. + /// + /// Type convert. + /// Conversion ability. + public override bool CanConvert(Type typeToConvert) + { + if (!typeToConvert.IsGenericType) + { + return false; + } + + // Let built in converter handle string keys + if (typeToConvert.GenericTypeArguments[0] == typeof(string)) + { + return false; + } + + // Only support objects that implement IDictionary + return typeToConvert.GetInterface(nameof(IDictionary)) != null; + } + + /// + /// Create converter for generic dictionary type. + /// + /// Type to convert. + /// Json serializer options. + /// JsonConverter for given type. + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + var converterType = typeof(JsonNonStringKeyDictionaryConverter<,>) + .MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]); + var converter = (JsonConverter)Activator.CreateInstance( + converterType, + BindingFlags.Instance | BindingFlags.Public, + null, + null, + CultureInfo.CurrentCulture); + return converter; + } + } +} diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 4a6ee0a79..78a458add 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Common.Json options.Converters.Add(new JsonGuidConverter()); options.Converters.Add(new JsonStringEnumConverter()); + options.Converters.Add(new JsonNonStringKeyDictionaryConverterFactory()); return options; } diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index f5571065f..c0324a384 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -1,5 +1,5 @@ using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Authentication diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 2639960e7..d9b814f69 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -1,6 +1,6 @@ using System; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Authentication diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index cdf2ca69e..dbb047804 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -3,6 +3,8 @@ using System.Globalization; using System.Linq; using System.Text.Json.Serialization; using System.Threading; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Querying; @@ -13,16 +15,18 @@ namespace MediaBrowser.Controller.Channels { public override bool IsVisible(User user) { - if (user.Policy.BlockedChannels != null) + if (user.GetPreference(PreferenceKind.BlockedChannels) != null) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (user.GetPreference(PreferenceKind.BlockedChannels).Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (!user.HasPermission(PermissionKind.EnableAllChannels) + && !user.GetPreference(PreferenceKind.EnabledChannels) + .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } diff --git a/MediaBrowser.Controller/Channels/ISearchableChannel.cs b/MediaBrowser.Controller/Channels/ISearchableChannel.cs index d5b76a160..48043ad7a 100644 --- a/MediaBrowser.Controller/Channels/ISearchableChannel.cs +++ b/MediaBrowser.Controller/Channels/ISearchableChannel.cs @@ -35,12 +35,10 @@ namespace MediaBrowser.Controller.Channels public interface IDisableMediaSourceDisplay { - } public interface ISupportsMediaProbe { - } public interface IHasFolderAttributes diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index cfe8493d3..701423c0f 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index ef3f43c75..7d279230b 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,5 +1,5 @@ using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 8800fdf99..f1873d539 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; @@ -57,6 +58,8 @@ namespace MediaBrowser.Controller.Drawing string GetImageCacheTag(BaseItem item, ChapterInfo info); + string GetImageCacheTag(User user); + /// /// Processes the image. /// diff --git a/MediaBrowser.Controller/Drawing/ImageHelper.cs b/MediaBrowser.Controller/Drawing/ImageHelper.cs index d5a5f547e..c87a248b5 100644 --- a/MediaBrowser.Controller/Drawing/ImageHelper.cs +++ b/MediaBrowser.Controller/Drawing/ImageHelper.cs @@ -57,6 +57,7 @@ namespace MediaBrowser.Controller.Drawing case ImageType.BoxRear: case ImageType.Disc: case ImageType.Menu: + case ImageType.Profile: return 1; case ImageType.Logo: return 2.58; diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index ba693a065..56e6c47c4 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index a700d0be4..a8ea2157d 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index b702bdb71..f7b2f9549 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { @@ -114,9 +115,9 @@ namespace MediaBrowser.Controller.Entities.Audio return list; } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Music); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString()); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 31136e004..63db3cfab 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.Audio { @@ -76,11 +77,7 @@ namespace MediaBrowser.Controller.Entities.Audio public override int GetChildCount(User user) { - if (IsAccessedByName) - { - return 0; - } - return base.GetChildCount(user); + return IsAccessedByName ? 0 : base.GetChildCount(user); } public override bool IsSaveLocalMetadataEnabled() @@ -142,9 +139,10 @@ namespace MediaBrowser.Controller.Entities.Audio { return "Artist-" + (Name ?? string.Empty).RemoveDiacritics(); } - protected override bool GetBlockUnratedValue(UserPolicy config) + + protected override bool GetBlockUnratedValue(User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Music); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music.ToString()); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index a13873bf9..4adaf4c6e 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -1,7 +1,7 @@ using System; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 98c416bb7..f2de1f2b1 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -7,6 +7,8 @@ using System.Text; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -24,7 +26,6 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -299,7 +300,7 @@ namespace MediaBrowser.Controller.Entities { get { - //if (IsOffline) + // if (IsOffline) //{ // return LocationType.Offline; //} @@ -481,12 +482,12 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsAuthorizedToDelete(User user, List allCollectionFolders) { - if (user.Policy.EnableContentDeletion) + if (user.HasPermission(PermissionKind.EnableContentDeletion)) { return true; } - var allowed = user.Policy.EnableContentDeletionFromFolders; + var allowed = user.GetPreference(PreferenceKind.EnableContentDeletionFromFolders); if (SourceType == SourceType.Channel) { @@ -527,7 +528,7 @@ namespace MediaBrowser.Controller.Entities public virtual bool IsAuthorizedToDownload(User user) { - return user.Policy.EnableContentDownloading; + return user.HasPermission(PermissionKind.EnableContentDownloading); } public bool CanDownload(User user) @@ -675,7 +676,7 @@ namespace MediaBrowser.Controller.Entities /// System.String. protected virtual string CreateSortName() { - if (Name == null) return null; //some items may not have name filled in properly + if (Name == null) return null; // some items may not have name filled in properly if (!EnableAlphaNumericSorting) { @@ -735,7 +736,7 @@ namespace MediaBrowser.Controller.Entities builder.Append(chunkBuilder); } - //logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); + // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); return builder.ToString().RemoveDiacritics(); } @@ -766,7 +767,6 @@ namespace MediaBrowser.Controller.Entities get => GetParent() as Folder; set { - } } @@ -1005,12 +1005,12 @@ namespace MediaBrowser.Controller.Entities /// PlayAccess. public PlayAccess GetPlayAccess(User user) { - if (!user.Policy.EnableMediaPlayback) + if (!user.HasPermission(PermissionKind.EnableMediaPlayback)) { return PlayAccess.None; } - //if (!user.IsParentalScheduleAllowed()) + // if (!user.IsParentalScheduleAllowed()) //{ // return PlayAccess.None; //} @@ -1063,7 +1063,6 @@ namespace MediaBrowser.Controller.Entities } return 1; - }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) .ThenByDescending(i => { @@ -1214,11 +1213,11 @@ namespace MediaBrowser.Controller.Entities { if (video.IsoType.HasValue) { - if (video.IsoType.Value == Model.Entities.IsoType.BluRay) + if (video.IsoType.Value == IsoType.BluRay) { terms.Add("Bluray"); } - else if (video.IsoType.Value == Model.Entities.IsoType.Dvd) + else if (video.IsoType.Value == IsoType.Dvd) { terms.Add("DVD"); } @@ -1246,8 +1245,7 @@ namespace MediaBrowser.Controller.Entities // Support plex/xbmc convention files.AddRange(fileSystemChildren - .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase)) - ); + .Where(i => !i.IsDirectory && string.Equals(FileSystem.GetFileNameWithoutExtension(i), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))); return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions()) .OfType() @@ -1346,12 +1344,10 @@ namespace MediaBrowser.Controller.Entities protected virtual void TriggerOnRefreshStart() { - } protected virtual void TriggerOnRefreshComplete() { - } /// @@ -1774,7 +1770,7 @@ namespace MediaBrowser.Controller.Entities return false; } - var maxAllowedRating = user.Policy.MaxParentalRating; + var maxAllowedRating = user.MaxParentalAgeRating; if (maxAllowedRating == null) { @@ -1790,7 +1786,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(rating)) { - return !GetBlockUnratedValue(user.Policy); + return !GetBlockUnratedValue(user); } var value = LocalizationManager.GetRatingLevel(rating); @@ -1798,7 +1794,7 @@ namespace MediaBrowser.Controller.Entities // Could not determine the integer value if (!value.HasValue) { - var isAllowed = !GetBlockUnratedValue(user.Policy); + var isAllowed = !GetBlockUnratedValue(user); if (!isAllowed) { @@ -1860,8 +1856,7 @@ namespace MediaBrowser.Controller.Entities private bool IsVisibleViaTags(User user) { - var policy = user.Policy; - if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) + if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase))) { return false; } @@ -1887,22 +1882,18 @@ namespace MediaBrowser.Controller.Entities /// /// Gets the block unrated value. /// - /// The configuration. + /// The configuration. /// true if XXXX, false otherwise. - protected virtual bool GetBlockUnratedValue(UserPolicy config) + protected virtual bool GetBlockUnratedValue(User user) { // Don't block plain folders that are unrated. Let the media underneath get blocked // Special folders like series and albums will override this method. - if (IsFolder) - { - return false; - } - if (this is IItemByName) + if (IsFolder || this is IItemByName) { return false; } - return config.BlockUnratedItems.Contains(GetBlockUnratedType()); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(GetBlockUnratedType().ToString()); } /// @@ -2132,7 +2123,8 @@ namespace MediaBrowser.Controller.Entities /// if set to true [reset position]. /// Task. /// - public virtual void MarkPlayed(User user, + public virtual void MarkPlayed( + User user, DateTime? datePlayed, bool resetPosition) { @@ -2178,7 +2170,7 @@ namespace MediaBrowser.Controller.Entities var data = UserDataManager.GetUserData(user, this); - //I think it is okay to do this here. + // I think it is okay to do this here. // if this is only called when a user is manually forcing something to un-played // then it probably is what we want to do... data.PlayCount = 0; @@ -2763,8 +2755,8 @@ namespace MediaBrowser.Controller.Entities newOptions.ForceSave = true; } - //var parentId = Id; - //if (!video.IsOwnedItem || video.ParentId != parentId) + // var parentId = Id; + // if (!video.IsOwnedItem || video.ParentId != parentId) //{ // video.IsOwnedItem = true; // video.ParentId = parentId; @@ -2806,14 +2798,7 @@ namespace MediaBrowser.Controller.Entities return this; } - foreach (var parent in GetParents()) - { - if (parent.IsTopParent) - { - return parent; - } - } - return null; + return GetParents().FirstOrDefault(parent => parent.IsTopParent); } [JsonIgnore] diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 62d172fcc..106385bc6 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool SupportsPeople => false; - //public override double? GetDefaultPrimaryImageAspectRatio() + // public override double? GetDefaultPrimaryImageAspectRatio() //{ // double value = 16; // value /= 9; diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index c5ce001c0..11c6c6e45 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,8 +1,8 @@ using System; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs b/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs deleted file mode 100644 index 8a79e0783..000000000 --- a/MediaBrowser.Controller/Entities/DayOfWeekHelper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Model.Configuration; - -namespace MediaBrowser.Controller.Entities -{ - public static class DayOfWeekHelper - { - public static List GetDaysOfWeek(DynamicDayOfWeek day) - { - return GetDaysOfWeek(new List { day }); - } - - public static List GetDaysOfWeek(List days) - { - var list = new List(); - - if (days.Contains(DynamicDayOfWeek.Sunday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Sunday); - } - - if (days.Contains(DynamicDayOfWeek.Saturday) || - days.Contains(DynamicDayOfWeek.Weekend) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Saturday); - } - - if (days.Contains(DynamicDayOfWeek.Monday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Monday); - } - - if (days.Contains(DynamicDayOfWeek.Tuesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Tuesday - ); - } - - if (days.Contains(DynamicDayOfWeek.Wednesday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Wednesday); - } - - if (days.Contains(DynamicDayOfWeek.Thursday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Thursday); - } - - if (days.Contains(DynamicDayOfWeek.Friday) || - days.Contains(DynamicDayOfWeek.Weekday) || - days.Contains(DynamicDayOfWeek.Everyday)) - { - list.Add(DayOfWeek.Friday); - } - - return list; - } - } -} diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 29040f92e..3a01b4379 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Collections; @@ -15,13 +17,16 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities { @@ -177,19 +182,22 @@ namespace MediaBrowser.Controller.Entities { if (this is ICollectionFolder && !(this is BasePluginFolder)) { - if (user.Policy.BlockedMediaFolders != null) + var blockedMediaFolders = user.GetPreference(PreferenceKind.BlockedMediaFolders); + if (blockedMediaFolders.Length > 0) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || + if (blockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility - user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) + blockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) + if (!user.HasPermission(PermissionKind.EnableAllFolders) + && !user.GetPreference(PreferenceKind.EnabledFolders) + .Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -205,8 +213,8 @@ namespace MediaBrowser.Controller.Entities /// protected virtual List LoadChildren() { - //logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path); - //just load our children from the repo - the library will be validated and maintained in other processes + // logger.LogDebug("Loading children from {0} {1} {2}", GetType().Name, Id, Path); + // just load our children from the repo - the library will be validated and maintained in other processes return GetCachedChildren(); } @@ -492,7 +500,6 @@ namespace MediaBrowser.Controller.Entities if (series != null) { await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false); - } await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false); } @@ -607,7 +614,6 @@ namespace MediaBrowser.Controller.Entities { EnableImages = false } - }); return result.TotalRecordCount; @@ -882,7 +888,7 @@ namespace MediaBrowser.Controller.Entities try { query.Parent = this; - query.ChannelIds = new Guid[] { ChannelId }; + query.ChannelIds = new[] { ChannelId }; // Don't blow up here because it could cause parent screens with other content to fail return ChannelManager.GetChannelItemsInternal(query, new SimpleProgress(), CancellationToken.None).Result; @@ -952,11 +958,13 @@ namespace MediaBrowser.Controller.Entities return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting); } - private static IEnumerable CollapseBoxSetItemsIfNeeded(IEnumerable items, + private static IEnumerable CollapseBoxSetItemsIfNeeded( + IEnumerable items, InternalItemsQuery query, BaseItem queryParent, User user, - IServerConfigurationManager configurationManager, ICollectionManager collectionManager) + IServerConfigurationManager configurationManager, + ICollectionManager collectionManager) { if (items == null) { @@ -1213,7 +1221,7 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException(nameof(user)); } - //the true root should return our users root folder children + // the true root should return our users root folder children if (IsPhysicalRoot) { return LibraryManager.GetUserRootFolder().GetChildren(user, includeLinkedChildren); @@ -1582,7 +1590,7 @@ namespace MediaBrowser.Controller.Entities EnableTotalRecordCount = false }; - if (!user.Configuration.DisplayMissingEpisodes) + if (!user.DisplayMissingEpisodes) { query.IsVirtualItem = false; } @@ -1619,7 +1627,6 @@ namespace MediaBrowser.Controller.Entities Recursive = true, IsFolder = false, EnableTotalRecordCount = false - }); // Sweep through recursively and update status @@ -1637,7 +1644,6 @@ namespace MediaBrowser.Controller.Entities IsFolder = false, IsVirtualItem = false, EnableTotalRecordCount = false - }); return itemsResult diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index bd96059e3..496bee857 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities @@ -223,15 +224,16 @@ namespace MediaBrowser.Controller.Entities { if (user != null) { - var policy = user.Policy; - MaxParentalRating = policy.MaxParentalRating; + MaxParentalRating = user.MaxParentalAgeRating; - if (policy.MaxParentalRating.HasValue) + if (MaxParentalRating.HasValue) { - BlockUnratedItems = policy.BlockUnratedItems.Where(i => i != UnratedItem.Other).ToArray(); + BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) + .Where(i => i != UnratedItem.Other.ToString()) + .Select(e => Enum.Parse(e, true)).ToArray(); } - ExcludeInheritedTags = policy.BlockedTags; + ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); User = user; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index feaf8c45a..be71bcc3c 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.Movies { @@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.Entities.Movies /// The display order. public string DisplayOrder { get; set; } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Movie); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Movie.ToString()); } public override double GetDefaultPrimaryImageAspectRatio() diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index d5c9ff025..26a165025 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -4,8 +4,8 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 603242063..6e7f2812d 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; namespace MediaBrowser.Controller.Entities { diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 49229fa4b..4ec60e7cd 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 9c8a469e2..7dfd1a759 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.TV { @@ -168,7 +168,7 @@ namespace MediaBrowser.Controller.Entities.TV return GetEpisodes(user, new DtoOptions(true)); } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(User user) { // Don't block. Let either the entire series rating or episode rating determine it return false; @@ -203,7 +203,7 @@ namespace MediaBrowser.Controller.Entities.TV public Guid FindSeriesId() { var series = FindParent(); - return series == null ? Guid.Empty : series.Id; + return series?.Id ?? Guid.Empty; } /// @@ -234,7 +234,7 @@ namespace MediaBrowser.Controller.Entities.TV if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) { - IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path); + IndexNumber ??= LibraryManager.GetSeasonNumberFromPath(Path); // If a change was made record it if (IndexNumber.HasValue) diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 6f1c2501f..a519089b3 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -5,13 +5,14 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Users; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; namespace MediaBrowser.Controller.Entities.TV { @@ -119,7 +120,7 @@ namespace MediaBrowser.Controller.Entities.TV { AncestorWithPresentationUniqueKey = null, SeriesPresentationUniqueKey = seriesKey, - IncludeItemTypes = new[] { typeof(Season).Name }, + IncludeItemTypes = new[] { nameof(Season) }, IsVirtualItem = false, Limit = 0, DtoOptions = new DtoOptions(false) @@ -205,14 +206,9 @@ namespace MediaBrowser.Controller.Entities.TV query.IncludeItemTypes = new[] { typeof(Season).Name }; query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(); - if (user != null) + if (user != null && !user.DisplayMissingEpisodes) { - var config = user.Configuration; - - if (!config.DisplayMissingEpisodes) - { - query.IsMissing = false; - } + query.IsMissing = false; } } @@ -257,8 +253,8 @@ namespace MediaBrowser.Controller.Entities.TV OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple(i, SortOrder.Ascending)).ToArray(), DtoOptions = options }; - var config = user.Configuration; - if (!config.DisplayMissingEpisodes) + + if (!user.DisplayMissingEpisodes) { query.IsMissing = false; } @@ -311,7 +307,7 @@ namespace MediaBrowser.Controller.Entities.TV // Refresh episodes and other children foreach (var item in items) { - if ((item is Season)) + if (item is Season) { continue; } @@ -370,8 +366,7 @@ namespace MediaBrowser.Controller.Entities.TV }; if (user != null) { - var config = user.Configuration; - if (!config.DisplayMissingEpisodes) + if (!user.DisplayMissingEpisodes) { query.IsMissing = false; } @@ -452,9 +447,9 @@ namespace MediaBrowser.Controller.Entities.TV } - protected override bool GetBlockUnratedValue(UserPolicy config) + protected override bool GetBlockUnratedValue(User user) { - return config.BlockUnratedItems.Contains(UnratedItem.Series); + return user.GetPreference(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Series.ToString()); } public override UnratedItem GetBlockUnratedType() diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 7159b5b1a..c327d17c9 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs deleted file mode 100644 index 53601a610..000000000 --- a/MediaBrowser.Controller/Entities/User.cs +++ /dev/null @@ -1,262 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Users; - -namespace MediaBrowser.Controller.Entities -{ - /// - /// Class User - /// - public class User : BaseItem - { - public static IUserManager UserManager { get; set; } - - /// - /// Gets or sets the password. - /// - /// The password. - public string Password { get; set; } - public string EasyPassword { get; set; } - - // Strictly to remove JsonIgnore - public override ItemImageInfo[] ImageInfos - { - get => base.ImageInfos; - set => base.ImageInfos = value; - } - - /// - /// Gets or sets the path. - /// - /// The path. - [JsonIgnore] - public override string Path - { - get => ConfigurationDirectoryPath; - set => base.Path = value; - } - - private string _name; - /// - /// Gets or sets the name. - /// - /// The name. - public override string Name - { - get => _name; - set - { - _name = value; - - // lazy load this again - SortName = null; - } - } - - /// - /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself - /// - /// The containing folder path. - [JsonIgnore] - public override string ContainingFolderPath => Path; - - /// - /// Gets the root folder. - /// - /// The root folder. - [JsonIgnore] - public Folder RootFolder => LibraryManager.GetUserRootFolder(); - - /// - /// Gets or sets the last login date. - /// - /// The last login date. - public DateTime? LastLoginDate { get; set; } - /// - /// Gets or sets the last activity date. - /// - /// The last activity date. - public DateTime? LastActivityDate { get; set; } - - private volatile UserConfiguration _config; - private readonly object _configSyncLock = new object(); - [JsonIgnore] - public UserConfiguration Configuration - { - get - { - if (_config == null) - { - lock (_configSyncLock) - { - if (_config == null) - { - _config = UserManager.GetUserConfiguration(this); - } - } - } - - return _config; - } - set => _config = value; - } - - private volatile UserPolicy _policy; - private readonly object _policySyncLock = new object(); - [JsonIgnore] - public UserPolicy Policy - { - get - { - if (_policy == null) - { - lock (_policySyncLock) - { - if (_policy == null) - { - _policy = UserManager.GetUserPolicy(this); - } - } - } - - return _policy; - } - set => _policy = value; - } - - /// - /// Renames the user. - /// - /// The new name. - /// Task. - /// - public Task Rename(string newName) - { - if (string.IsNullOrWhiteSpace(newName)) - { - throw new ArgumentException("Username can't be empty", nameof(newName)); - } - - Name = newName; - - return RefreshMetadata( - new MetadataRefreshOptions(new DirectoryService(FileSystem)) - { - ReplaceAllMetadata = true, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = true - - }, - CancellationToken.None); - } - - public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) - { - UserManager.UpdateUser(this); - } - - /// - /// Gets the path to the user's configuration directory - /// - /// The configuration directory path. - [JsonIgnore] - public string ConfigurationDirectoryPath => GetConfigurationDirectoryPath(Name); - - public override double GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - - /// - /// Gets the configuration directory path. - /// - /// The username. - /// System.String. - private string GetConfigurationDirectoryPath(string username) - { - var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath; - - // TODO: Remove idPath and just use usernamePath for future releases - var usernamePath = System.IO.Path.Combine(parentPath, username); - var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); - if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) - { - Directory.Move(idPath, usernamePath); - } - - return usernamePath; - } - - public bool IsParentalScheduleAllowed() - { - return IsParentalScheduleAllowed(DateTime.UtcNow); - } - - public bool IsParentalScheduleAllowed(DateTime date) - { - var schedules = Policy.AccessSchedules; - - if (schedules.Length == 0) - { - return true; - } - - foreach (var i in schedules) - { - if (IsParentalScheduleAllowed(i, date)) - { - return true; - } - } - return false; - } - - private bool IsParentalScheduleAllowed(AccessSchedule schedule, DateTime date) - { - if (date.Kind != DateTimeKind.Utc) - { - throw new ArgumentException("Utc date expected"); - } - - var localTime = date.ToLocalTime(); - - return DayOfWeekHelper.GetDaysOfWeek(schedule.DayOfWeek).Contains(localTime.DayOfWeek) && - IsWithinTime(schedule, localTime); - } - - private bool IsWithinTime(AccessSchedule schedule, DateTime localTime) - { - var hour = localTime.TimeOfDay.TotalHours; - - return hour >= schedule.StartHour && hour <= schedule.EndHour; - } - - public bool IsFolderGrouped(Guid id) - { - foreach (var i in Configuration.GroupedFolders) - { - if (new Guid(i) == id) - { - return true; - } - } - return false; - } - - [JsonIgnore] - public override bool SupportsPeople => false; - - public long InternalId { get; set; } - - - } -} diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 8a68f830c..39f4e0b6c 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 86cff632c..ceca77bc0 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; @@ -111,7 +112,7 @@ namespace MediaBrowser.Controller.Entities private static string[] UserSpecificViewTypes = new string[] { - MediaBrowser.Model.Entities.CollectionType.Playlists + Model.Entities.CollectionType.Playlists }; public static bool IsUserSpecific(Folder folder) @@ -140,8 +141,8 @@ namespace MediaBrowser.Controller.Entities private static string[] ViewTypesEligibleForGrouping = new string[] { - MediaBrowser.Model.Entities.CollectionType.Movies, - MediaBrowser.Model.Entities.CollectionType.TvShows, + Model.Entities.CollectionType.Movies, + Model.Entities.CollectionType.TvShows, string.Empty }; @@ -152,12 +153,12 @@ namespace MediaBrowser.Controller.Entities private static string[] OriginalFolderViewTypes = new string[] { - MediaBrowser.Model.Entities.CollectionType.Books, - MediaBrowser.Model.Entities.CollectionType.MusicVideos, - MediaBrowser.Model.Entities.CollectionType.HomeVideos, - MediaBrowser.Model.Entities.CollectionType.Photos, - MediaBrowser.Model.Entities.CollectionType.Music, - MediaBrowser.Model.Entities.CollectionType.BoxSets + Model.Entities.CollectionType.Books, + Model.Entities.CollectionType.MusicVideos, + Model.Entities.CollectionType.HomeVideos, + Model.Entities.CollectionType.Photos, + Model.Entities.CollectionType.Music, + Model.Entities.CollectionType.BoxSets }; public static bool EnableOriginalFolder(string viewType) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index db3e67f0d..dbfef0777 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -2,14 +2,19 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Controller.Entities { @@ -42,7 +47,7 @@ namespace MediaBrowser.Controller.Entities { var user = query.User; - //if (query.IncludeItemTypes != null && + // if (query.IncludeItemTypes != null && // query.IncludeItemTypes.Length == 1 && // string.Equals(query.IncludeItemTypes[0], "Playlist", StringComparison.OrdinalIgnoreCase)) //{ @@ -140,14 +145,15 @@ namespace MediaBrowser.Controller.Entities return parent.QueryRecursive(query); } - var list = new List(); - - list.Add(GetUserView(SpecialFolder.MovieResume, "HeaderContinueWatching", "0", parent)); - list.Add(GetUserView(SpecialFolder.MovieLatest, "Latest", "1", parent)); - list.Add(GetUserView(SpecialFolder.MovieMovies, "Movies", "2", parent)); - list.Add(GetUserView(SpecialFolder.MovieCollections, "Collections", "3", parent)); - list.Add(GetUserView(SpecialFolder.MovieFavorites, "Favorites", "4", parent)); - list.Add(GetUserView(SpecialFolder.MovieGenres, "Genres", "5", parent)); + var list = new List + { + GetUserView(SpecialFolder.MovieResume, "HeaderContinueWatching", "0", parent), + GetUserView(SpecialFolder.MovieLatest, "Latest", "1", parent), + GetUserView(SpecialFolder.MovieMovies, "Movies", "2", parent), + GetUserView(SpecialFolder.MovieCollections, "Collections", "3", parent), + GetUserView(SpecialFolder.MovieFavorites, "Favorites", "4", parent), + GetUserView(SpecialFolder.MovieGenres, "Genres", "5", parent) + }; return GetResult(list, parent, query); } @@ -264,7 +270,6 @@ namespace MediaBrowser.Controller.Entities _logger.LogError(ex, "Error getting genre"); return null; } - }) .Where(i => i != null) .Select(i => GetUserViewWithName(i.Name, SpecialFolder.MovieGenre, i.SortName, parent)); @@ -293,21 +298,27 @@ namespace MediaBrowser.Controller.Entities if (query.IncludeItemTypes.Length == 0) { - query.IncludeItemTypes = new[] { typeof(Series).Name, typeof(Season).Name, typeof(Episode).Name }; + query.IncludeItemTypes = new[] + { + nameof(Series), + nameof(Season), + nameof(Episode) + }; } return parent.QueryRecursive(query); } - var list = new List(); - - list.Add(GetUserView(SpecialFolder.TvResume, "HeaderContinueWatching", "0", parent)); - list.Add(GetUserView(SpecialFolder.TvNextUp, "HeaderNextUp", "1", parent)); - list.Add(GetUserView(SpecialFolder.TvLatest, "Latest", "2", parent)); - list.Add(GetUserView(SpecialFolder.TvShowSeries, "Shows", "3", parent)); - list.Add(GetUserView(SpecialFolder.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent)); - list.Add(GetUserView(SpecialFolder.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent)); - list.Add(GetUserView(SpecialFolder.TvGenres, "Genres", "6", parent)); + var list = new List + { + GetUserView(SpecialFolder.TvResume, "HeaderContinueWatching", "0", parent), + GetUserView(SpecialFolder.TvNextUp, "HeaderNextUp", "1", parent), + GetUserView(SpecialFolder.TvLatest, "Latest", "2", parent), + GetUserView(SpecialFolder.TvShowSeries, "Shows", "3", parent), + GetUserView(SpecialFolder.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), + GetUserView(SpecialFolder.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), + GetUserView(SpecialFolder.TvGenres, "Genres", "6", parent) + }; return GetResult(list, parent, query); } @@ -335,7 +346,6 @@ namespace MediaBrowser.Controller.Entities Limit = query.Limit, StartIndex = query.StartIndex, UserId = query.User.Id - }, parentFolders, query.DtoOptions); return result; @@ -372,7 +382,6 @@ namespace MediaBrowser.Controller.Entities IncludeItemTypes = new[] { typeof(Series).Name }, Recursive = true, EnableTotalRecordCount = false - }).Items .SelectMany(i => i.Genres) .DistinctNames() @@ -387,7 +396,6 @@ namespace MediaBrowser.Controller.Entities _logger.LogError(ex, "Error getting genre"); return null; } - }) .Where(i => i != null) .Select(i => GetUserViewWithName(i.Name, SpecialFolder.TvGenre, i.SortName, parent)); @@ -412,12 +420,13 @@ namespace MediaBrowser.Controller.Entities { return new QueryResult { - Items = result.Items, //TODO Fix The co-variant conversion between T[] and BaseItem[], this can generate runtime issues if T is not BaseItem. + Items = result.Items, // TODO Fix The co-variant conversion between T[] and BaseItem[], this can generate runtime issues if T is not BaseItem. TotalRecordCount = result.TotalRecordCount }; } - private QueryResult GetResult(IEnumerable items, + private QueryResult GetResult( + IEnumerable items, BaseItem queryParent, InternalItemsQuery query) where T : BaseItem diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 72eb67a06..4cfa0e74d 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -535,7 +535,6 @@ namespace MediaBrowser.Controller.Entities { ItemId = Id, Index = DefaultVideoStreamIndex.Value - }).FirstOrDefault(); } diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs index 666a3f76b..aa7373815 100644 --- a/MediaBrowser.Controller/IO/FileData.cs +++ b/MediaBrowser.Controller/IO/FileData.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.Controller.IO if (string.IsNullOrEmpty(newPath)) { - //invalid shortcut - could be old or target could just be unavailable + // invalid shortcut - could be old or target could just be unavailable logger.LogWarning("Encountered invalid shortcut: " + fullName); continue; } @@ -118,7 +118,6 @@ namespace MediaBrowser.Controller.IO } return returnResult; } - } } diff --git a/MediaBrowser.Controller/Library/IIntroProvider.cs b/MediaBrowser.Controller/Library/IIntroProvider.cs index d9d1ca8c7..aa7001611 100644 --- a/MediaBrowser.Controller/Library/IIntroProvider.cs +++ b/MediaBrowser.Controller/Library/IIntroProvider.cs @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Library /// The item. /// The user. /// IEnumerable{System.String}. - Task> GetIntros(BaseItem item, User user); + Task> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user); /// /// Gets all intro files. diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 5afe356f4..d7237039e 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -2,10 +2,10 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; @@ -14,6 +14,9 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Genre = MediaBrowser.Controller.Entities.Genre; +using Person = MediaBrowser.Controller.Entities.Person; namespace MediaBrowser.Controller.Library { @@ -289,7 +292,8 @@ namespace MediaBrowser.Controller.Library /// The parent identifier. /// Type of the view. /// Name of the sort. - UserView GetNamedView(User user, + UserView GetNamedView( + User user, string name, Guid parentId, string viewType, @@ -302,7 +306,8 @@ namespace MediaBrowser.Controller.Library /// The name. /// Type of the view. /// Name of the sort. - UserView GetNamedView(User user, + UserView GetNamedView( + User user, string name, string viewType, string sortName); diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs index 0ceabd0e6..94528ff77 100644 --- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs +++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index 554dd0895..36b250ec9 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index eb735d31a..f5ccad671 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; @@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Library /// The reason. /// The cancellation token. void SaveUserData(Guid userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); - void SaveUserData(User userId, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); + void SaveUserData(User user, BaseItem item, UserItemData userData, UserDataSaveReason reason, CancellationToken cancellationToken); UserItemData GetUserData(User user, BaseItem item); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index be7b4ce59..b5b2e4729 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Events; @@ -16,6 +15,31 @@ namespace MediaBrowser.Controller.Library /// public interface IUserManager { + /// + /// Occurs when a user is updated. + /// + event EventHandler> OnUserUpdated; + + /// + /// Occurs when a user is created. + /// + event EventHandler> OnUserCreated; + + /// + /// Occurs when a user is deleted. + /// + event EventHandler> OnUserDeleted; + + /// + /// Occurs when a user's password is changed. + /// + event EventHandler> OnUserPasswordChanged; + + /// + /// Occurs when a user is locked out. + /// + event EventHandler> OnUserLockedOut; + /// /// Gets the users. /// @@ -29,24 +53,9 @@ namespace MediaBrowser.Controller.Library IEnumerable UsersIds { get; } /// - /// Occurs when [user updated]. + /// Initializes the user manager and ensures that a user exists. /// - event EventHandler> UserUpdated; - - /// - /// Occurs when [user deleted]. - /// - event EventHandler> UserDeleted; - - event EventHandler> UserCreated; - - event EventHandler> UserPolicyUpdated; - - event EventHandler> UserConfigurationUpdated; - - event EventHandler> UserPasswordChanged; - - event EventHandler> UserLockedOut; + void Initialize(); /// /// Gets a user by Id. @@ -63,13 +72,6 @@ namespace MediaBrowser.Controller.Library /// User. User GetUserByName(string name); - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - Task RefreshUsersMetadata(CancellationToken cancellationToken); - /// /// Renames the user. /// @@ -89,19 +91,27 @@ namespace MediaBrowser.Controller.Library void UpdateUser(User user); /// - /// Creates the user. + /// Updates the user. /// - /// The name. - /// User. + /// The user. + /// If user is null. + /// If the provided user doesn't exist. + /// A task representing the update of the user. + Task UpdateUserAsync(User user); + + /// + /// Creates a user with the specified name. + /// + /// The name of the new user. + /// The created user. /// name /// User CreateUser(string name); /// - /// Deletes the user. + /// Deletes the specified user. /// - /// The user. - /// Task. + /// The user to be deleted. void DeleteUser(User user); /// @@ -111,13 +121,6 @@ namespace MediaBrowser.Controller.Library /// Task. Task ResetPassword(User user); - /// - /// Gets the offline user dto. - /// - /// The user. - /// UserDto. - UserDto GetOfflineUserDto(User user); - /// /// Resets the easy password. /// @@ -163,47 +166,34 @@ namespace MediaBrowser.Controller.Library /// true if XXXX, false otherwise. Task RedeemPasswordResetPin(string pin); - /// - /// Gets the user policy. - /// - /// The user. - /// UserPolicy. - UserPolicy GetUserPolicy(User user); - - /// - /// Gets the user configuration. - /// - /// The user. - /// UserConfiguration. - UserConfiguration GetUserConfiguration(User user); - - /// - /// Updates the configuration. - /// - /// The user identifier. - /// The new configuration. - /// Task. - void UpdateConfiguration(Guid userId, UserConfiguration newConfiguration); - - void UpdateConfiguration(User user, UserConfiguration newConfiguration); - - /// - /// Updates the user policy. - /// - /// The user identifier. - /// The user policy. - void UpdateUserPolicy(Guid userId, UserPolicy userPolicy); - - /// - /// Makes the valid username. - /// - /// The username. - /// System.String. - string MakeValidUsername(string username); - void AddParts(IEnumerable authenticationProviders, IEnumerable passwordResetProviders); NameIdPair[] GetAuthenticationProviders(); + NameIdPair[] GetPasswordResetProviders(); + + /// + /// This method updates the user's configuration. + /// This is only included as a stopgap until the new API, using this internally is not recommended. + /// Instead, modify the user object directly, then call . + /// + /// The user's Id. + /// The request containing the new user configuration. + void UpdateConfiguration(Guid userId, UserConfiguration config); + + /// + /// This method updates the user's policy. + /// This is only included as a stopgap until the new API, using this internally is not recommended. + /// Instead, modify the user object directly, then call . + /// + /// The user's Id. + /// The request containing the new user policy. + void UpdatePolicy(Guid userId, UserPolicy policy); + + /// + /// Clears the user's profile image. + /// + /// The user. + void ClearProfileImage(User user); } } diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index 0222b926e..cca85cd3b 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -89,7 +89,6 @@ namespace MediaBrowser.Controller.Library return parentDir.Length > _appPaths.RootFolderPath.Length && parentDir.StartsWith(_appPaths.RootFolderPath, StringComparison.OrdinalIgnoreCase); - } } @@ -129,7 +128,6 @@ namespace MediaBrowser.Controller.Library } return item != null; - } return false; } diff --git a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs index b0302d04c..b4e205184 100644 --- a/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs +++ b/MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index e02c387e4..bc3bf78f0 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 60391bb83..10af98121 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; diff --git a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs index df8f91eec..92b8ee67c 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvConflictException.cs @@ -9,12 +9,10 @@ namespace MediaBrowser.Controller.LiveTv { public LiveTvConflictException() { - } public LiveTvConflictException(string message) : base(message) { - } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index e89bc05aa..e17db34c6 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -3,10 +3,10 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Providers; @@ -147,7 +147,7 @@ namespace MediaBrowser.Controller.LiveTv public override string ContainingFolderPath => Path; //[JsonIgnore] - //public override string MediaType + // public override string MediaType //{ // get // { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2d2f73148..0ca42c0e0 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Extensions; using MediaBrowser.Model.Configuration; @@ -758,7 +759,6 @@ namespace MediaBrowser.Controller.MediaEncoding } param += " -look_ahead 0"; - } else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) @@ -767,7 +767,7 @@ namespace MediaBrowser.Controller.MediaEncoding { case "veryslow": - param += "-preset slow"; //lossless is only supported on maxwell and newer(2014+) + param += "-preset slow"; // lossless is only supported on maxwell and newer(2014+) break; case "slow": @@ -998,7 +998,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (string.IsNullOrEmpty(videoStream.Profile)) { - //return false; + // return false; } var requestedProfile = requestedProfiles[0]; @@ -1071,7 +1071,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!videoStream.Level.HasValue) { - //return false; + // return false; } if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) @@ -1326,7 +1326,6 @@ namespace MediaBrowser.Controller.MediaEncoding // wmav2 currently only supports two channel output transcoderChannelLimit = 2; } - else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1) { // libmp3lame currently only supports two channel output @@ -1461,7 +1460,6 @@ namespace MediaBrowser.Controller.MediaEncoding " -map 0:{0}", state.AudioStream.Index); } - else { args += " -map -0:a"; @@ -1647,7 +1645,6 @@ namespace MediaBrowser.Controller.MediaEncoding outputSizeParam = outputSizeParam.TrimStart(','); retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } - else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { /* @@ -1932,11 +1929,11 @@ namespace MediaBrowser.Controller.MediaEncoding break; case Video3DFormat.FullSideBySide: filter = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth. + // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth. break; case Video3DFormat.HalfTopAndBottom: filter = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; - //htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth + // htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth break; case Video3DFormat.FullTopAndBottom: filter = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; @@ -2148,7 +2145,7 @@ namespace MediaBrowser.Controller.MediaEncoding var user = state.User; // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableVideoPlaybackTranscoding) + if (user != null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)) { state.OutputVideoCodec = "copy"; } @@ -2164,7 +2161,7 @@ namespace MediaBrowser.Controller.MediaEncoding var user = state.User; // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not - if (user != null && !user.Policy.EnableAudioPlaybackTranscoding) + if (user != null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)) { state.OutputAudioCodec = "copy"; } @@ -2555,7 +2552,7 @@ namespace MediaBrowser.Controller.MediaEncoding case "h265": if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) { - //return "-c:v hevc_qsv -load_plugin hevc_hw "; + // return "-c:v hevc_qsv -load_plugin hevc_hw "; return "-c:v hevc_qsv"; } break; @@ -2573,7 +2570,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2618,7 +2614,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2663,7 +2658,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -2695,7 +2689,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { if (Environment.OSVersion.Platform == PlatformID.Win32NT) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 7cd783595..acf1aae89 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -18,22 +18,37 @@ namespace MediaBrowser.Controller.MediaEncoding public class EncodingJobInfo { public MediaStream VideoStream { get; set; } + public VideoType VideoType { get; set; } + public Dictionary RemoteHttpHeaders { get; set; } + public string OutputVideoCodec { get; set; } + public MediaProtocol InputProtocol { get; set; } + public string MediaPath { get; set; } + public bool IsInputVideo { get; set; } + public IIsoMount IsoMount { get; set; } + public string[] PlayableStreamFileNames { get; set; } + public string OutputAudioCodec { get; set; } + public int? OutputVideoBitrate { get; set; } + public MediaStream SubtitleStream { get; set; } + public SubtitleDeliveryMethod SubtitleDeliveryMethod { get; set; } + public string[] SupportedSubtitleCodecs { get; set; } public int InternalSubtitleStreamOffset { get; set; } + public MediaSourceInfo MediaSource { get; set; } + public User User { get; set; } public long? RunTimeTicks { get; set; } diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs index 3e004763d..4361e253b 100644 --- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs +++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs @@ -1,36 +1,40 @@ using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Net { public class AuthorizationInfo { /// - /// Gets or sets the user identifier. + /// Gets the user identifier. /// /// The user identifier. - public Guid UserId => User == null ? Guid.Empty : User.Id; + public Guid UserId => User?.Id ?? Guid.Empty; /// /// Gets or sets the device identifier. /// /// The device identifier. public string DeviceId { get; set; } + /// /// Gets or sets the device. /// /// The device. public string Device { get; set; } + /// /// Gets or sets the client. /// /// The client. public string Client { get; set; } + /// /// Gets or sets the version. /// /// The version. public string Version { get; set; } + /// /// Gets or sets the token. /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 7dca793c6..df90c399b 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -156,6 +156,7 @@ namespace MediaBrowser.Controller.Net await connection.SendAsync( new WebSocketMessage { + MessageId = Guid.NewGuid(), MessageType = Name, Data = data }, @@ -213,7 +214,7 @@ namespace MediaBrowser.Controller.Net } catch (ObjectDisposedException) { - //TODO Investigate and properly fix. + // TODO Investigate and properly fix. } lock (_activeConnections) diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 9132404a0..d8f6d19da 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,6 +1,6 @@ #nullable enable -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; @@ -9,6 +9,7 @@ namespace MediaBrowser.Controller.Net public interface IAuthService { void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); + User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); } } diff --git a/MediaBrowser.Controller/Net/ISessionContext.cs b/MediaBrowser.Controller/Net/ISessionContext.cs index 5c3c19f6b..421ac3fe2 100644 --- a/MediaBrowser.Controller/Net/ISessionContext.cs +++ b/MediaBrowser.Controller/Net/ISessionContext.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs index 8c6019923..ab5eb13cd 100644 --- a/MediaBrowser.Controller/Notifications/INotificationService.cs +++ b/MediaBrowser.Controller/Notifications/INotificationService.cs @@ -1,6 +1,6 @@ using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; namespace MediaBrowser.Controller.Notifications { diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs index 3f46468b3..a1029589b 100644 --- a/MediaBrowser.Controller/Notifications/UserNotification.cs +++ b/MediaBrowser.Controller/Notifications/UserNotification.cs @@ -1,5 +1,5 @@ using System; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Notifications; namespace MediaBrowser.Controller.Notifications diff --git a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs index a4bdf60d7..4c327eeef 100644 --- a/MediaBrowser.Controller/Persistence/IUserDataRepository.cs +++ b/MediaBrowser.Controller/Persistence/IUserDataRepository.cs @@ -44,6 +44,5 @@ namespace MediaBrowser.Controller.Persistence /// /// void SaveAllUserData(long userId, UserItemData[] userData, CancellationToken cancellationToken); - } } diff --git a/MediaBrowser.Controller/Persistence/IUserRepository.cs b/MediaBrowser.Controller/Persistence/IUserRepository.cs deleted file mode 100644 index cd23e5223..000000000 --- a/MediaBrowser.Controller/Persistence/IUserRepository.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Controller.Entities; - -namespace MediaBrowser.Controller.Persistence -{ - /// - /// Provides an interface to implement a User repository - /// - public interface IUserRepository : IRepository - { - /// - /// Deletes the user. - /// - /// The user. - /// Task. - void DeleteUser(User user); - - /// - /// Retrieves all users. - /// - /// IEnumerable{User}. - List RetrieveAllUsers(); - - void CreateUser(User user); - void UpdateUser(User user); - } -} diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 3b08e72b9..b1a638883 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -241,15 +242,7 @@ namespace MediaBrowser.Controller.Playlists } var userId = user.Id.ToString("N", CultureInfo.InvariantCulture); - foreach (var share in shares) - { - if (string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; + return shares.Any(share => string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)); } public override bool IsVisibleStandalone(User user) diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs index 1e8654c4d..b44e2531e 100644 --- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs +++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs @@ -22,6 +22,5 @@ namespace MediaBrowser.Controller.Plugins /// public interface IRunBeforeStartup { - } } diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 254b27460..955db0278 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -70,6 +71,8 @@ namespace MediaBrowser.Controller.Providers /// Task. Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken); + Task SaveImage(User user, Stream source, string mimeType, string path); + /// /// Adds the metadata providers. /// diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 4c2f834cb..1fdb588eb 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Dto; @@ -75,7 +74,7 @@ namespace MediaBrowser.Controller.Session /// Name of the device. /// The remote end point. /// The user. - SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, User user); + SessionInfo LogSessionActivity(string appName, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Jellyfin.Data.Entities.User user); void UpdateDeviceName(string sessionId, string reportedDeviceName); diff --git a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs index 1e2df37bf..6f75d16de 100644 --- a/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs @@ -1,4 +1,3 @@ -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; namespace MediaBrowser.Controller.Sorting @@ -12,7 +11,7 @@ namespace MediaBrowser.Controller.Sorting /// Gets or sets the user. /// /// The user. - User User { get; set; } + Jellyfin.Data.Entities.User User { get; set; } /// /// Gets or sets the user manager. diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs index 28a3ac505..ef8df7d02 100644 --- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs +++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs @@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.SyncPlay public class GroupInfo { /// - /// Default ping value used for sessions. + /// Gets the default ping value used for sessions. /// public long DefaulPing { get; } = 500; @@ -31,13 +31,13 @@ namespace MediaBrowser.Controller.SyncPlay public BaseItem PlayingItem { get; set; } /// - /// Gets or sets whether playback is paused. + /// Gets or sets a value indicating whether playback is paused. /// /// Playback is paused. public bool IsPaused { get; set; } /// - /// Gets or sets the position ticks. + /// Gets or sets a value indicating whether there are position ticks. /// /// The position ticks. public long PositionTicks { get; set; } diff --git a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs index a6553563f..4770d2bee 100644 --- a/MediaBrowser.LocalMetadata/BaseXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/BaseXmlProvider.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.LocalMetadata public string Name => XmlProviderUtils.Name; - //After Nfo + // After Nfo public virtual int Order => 1; } diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 4f1112396..0ceb55c57 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -78,10 +78,10 @@ namespace MediaBrowser.LocalMetadata.Parsers } } - //Additional Mappings + // Additional Mappings _validProviderIds.Add("IMDB", "Imdb"); - //Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken); + // Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken); Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken); } @@ -255,7 +255,6 @@ namespace MediaBrowser.LocalMetadata.Parsers } return null; - }).Where(i => i.HasValue).Select(i => i.Value).ToArray(); } @@ -680,7 +679,6 @@ namespace MediaBrowser.LocalMetadata.Parsers } break; - } } } @@ -1260,6 +1258,5 @@ namespace MediaBrowser.LocalMetadata.Parsers { return val.Split(separators, options); } - } } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index a82f2108f..f02999370 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -269,7 +269,6 @@ namespace MediaBrowser.MediaEncoding.Attachments if (disposing) { - } _disposed = true; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 26bd202ba..a4896d5f9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -247,14 +247,14 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetAvailableEncoders(IEnumerable list) { _encoders = list.ToList(); - //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); + // _logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray())); } private List _decoders = new List(); public void SetAvailableDecoders(IEnumerable list) { _decoders = list.ToList(); - //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); + // _logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray())); } public bool SupportsEncoder(string encoder) @@ -500,11 +500,11 @@ namespace MediaBrowser.MediaEncoding.Encoder break; case Video3DFormat.FullSideBySide: vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. + // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600. break; case Video3DFormat.HalfTopAndBottom: vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; - //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 + // htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600 break; case Video3DFormat.FullTopAndBottom: vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2"; @@ -920,7 +920,6 @@ namespace MediaBrowser.MediaEncoding.Encoder var fileParts = _fileSystem.GetFileNameWithoutExtension(f).Split('_'); return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase); - }).ToList(); // If this resulted in not getting any vobs, just take them all diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 9386d6524..7d57a691e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -413,7 +413,6 @@ namespace MediaBrowser.MediaEncoding.Probing .Where(i => !string.IsNullOrWhiteSpace(i)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); - } else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase)) { @@ -425,7 +424,6 @@ namespace MediaBrowser.MediaEncoding.Probing Type = PersonType.Writer }); } - } else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase)) { @@ -562,7 +560,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (string.Equals(streamInfo.CodecName, "mov_text", StringComparison.OrdinalIgnoreCase)) { // Edit: but these are also sometimes subtitles? - //return null; + // return null; } var stream = new MediaStream @@ -684,7 +682,7 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitDepth = streamInfo.BitsPerRawSample; } - //stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || + // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); @@ -953,8 +951,8 @@ namespace MediaBrowser.MediaEncoding.Probing audio.People = peoples.ToArray(); } - //var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); - //if (!string.IsNullOrWhiteSpace(conductor)) + // var conductor = FFProbeHelpers.GetDictionaryValue(tags, "conductor"); + // if (!string.IsNullOrWhiteSpace(conductor)) //{ // foreach (var person in Split(conductor, false)) // { @@ -962,8 +960,8 @@ namespace MediaBrowser.MediaEncoding.Probing // } //} - //var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); - //if (!string.IsNullOrWhiteSpace(lyricist)) + // var lyricist = FFProbeHelpers.GetDictionaryValue(tags, "lyricist"); + // if (!string.IsNullOrWhiteSpace(lyricist)) //{ // foreach (var person in Split(lyricist, false)) // { @@ -1028,7 +1026,6 @@ namespace MediaBrowser.MediaEncoding.Probing audio.AlbumArtists = SplitArtists(albumArtist, _nameDelimiters, true) .DistinctNames() .ToArray(); - } if (audio.AlbumArtists.Length == 0) diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index b94d45165..bae2f5417 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -130,10 +130,10 @@ namespace MediaBrowser.MediaEncoding.Subtitles } } - //if (header.Length > 0) - //subtitle.Header = header.ToString(); + // if (header.Length > 0) + // subtitle.Header = header.ToString(); - //subtitle.Renumber(1); + // subtitle.Renumber(1); } trackInfo.TrackEvents = trackEvents.ToArray(); return trackInfo; @@ -261,7 +261,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles text += ""; } } - } text = text.Replace(@"{\i1}", ""); diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index f08af6045..7e9894f0a 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -640,7 +640,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles } catch (FileNotFoundException) { - } catch (IOException ex) { diff --git a/MediaBrowser.Model/Configuration/AccessSchedule.cs b/MediaBrowser.Model/Configuration/AccessSchedule.cs index 120c47dbc..7bd355449 100644 --- a/MediaBrowser.Model/Configuration/AccessSchedule.cs +++ b/MediaBrowser.Model/Configuration/AccessSchedule.cs @@ -1,3 +1,5 @@ +using Jellyfin.Data.Enums; + #pragma warning disable CS1591 namespace MediaBrowser.Model.Configuration diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 289047d6b..85d864eec 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -2,6 +2,7 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; namespace MediaBrowser.Model.Configuration { diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs index b055ad41a..a579f8464 100644 --- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs +++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs @@ -79,11 +79,11 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.InteractiveTransferMode | DlnaFlags.DlnaV15; - //if (isDirectStream) + // if (isDirectStream) //{ // flagValue = flagValue | DlnaFlags.ByteBasedSeek; //} - //else if (runtimeTicks.HasValue) + // else if (runtimeTicks.HasValue) //{ // flagValue = flagValue | DlnaFlags.TimeBasedSeek; //} @@ -148,11 +148,11 @@ namespace MediaBrowser.Model.Dlna DlnaFlags.InteractiveTransferMode | DlnaFlags.DlnaV15; - //if (isDirectStream) + // if (isDirectStream) //{ // flagValue = flagValue | DlnaFlags.ByteBasedSeek; //} - //else if (runtimeTicks.HasValue) + // else if (runtimeTicks.HasValue) //{ // flagValue = flagValue | DlnaFlags.TimeBasedSeek; //} diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs index 10e9179c0..c03a8060f 100644 --- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs +++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs @@ -158,7 +158,6 @@ namespace MediaBrowser.Model.Dlna return new MediaFormatProfile[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) }; } - } else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Model/Dlna/ProfileCondition.cs b/MediaBrowser.Model/Dlna/ProfileCondition.cs index f8b5dee81..4b39d6875 100644 --- a/MediaBrowser.Model/Dlna/ProfileCondition.cs +++ b/MediaBrowser.Model/Dlna/ProfileCondition.cs @@ -15,7 +15,6 @@ namespace MediaBrowser.Model.Dlna public ProfileCondition(ProfileConditionType condition, ProfileConditionValue property, string value) : this(condition, property, value, false) { - } public ProfileCondition(ProfileConditionType condition, ProfileConditionValue property, string value, bool isRequired) diff --git a/MediaBrowser.Model/Dlna/SortCriteria.cs b/MediaBrowser.Model/Dlna/SortCriteria.cs index 3f8985fdc..1f7fa76ad 100644 --- a/MediaBrowser.Model/Dlna/SortCriteria.cs +++ b/MediaBrowser.Model/Dlna/SortCriteria.cs @@ -10,7 +10,6 @@ namespace MediaBrowser.Model.Dlna public SortCriteria(string value) { - } } } diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index a18ad36c5..3fe5cf774 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -109,7 +109,6 @@ namespace MediaBrowser.Model.Dlna } return 1; - }).ThenBy(i => { switch (i.PlayMethod) @@ -121,7 +120,6 @@ namespace MediaBrowser.Model.Dlna default: return 1; } - }).ThenBy(i => { switch (i.MediaSource.Protocol) @@ -131,7 +129,6 @@ namespace MediaBrowser.Model.Dlna default: return 1; } - }).ThenBy(i => { if (maxBitrate > 0) @@ -143,7 +140,6 @@ namespace MediaBrowser.Model.Dlna } return 0; - }).ThenBy(streams.IndexOf); } @@ -781,7 +777,7 @@ namespace MediaBrowser.Model.Dlna if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { - //LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); + // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); applyConditions = false; break; } @@ -825,7 +821,7 @@ namespace MediaBrowser.Model.Dlna if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, inputAudioBitrate, inputAudioSampleRate, inputAudioBitDepth, audioProfile, isSecondaryAudio)) { - //LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); + // LogConditionFailure(options.Profile, "VideoCodecProfile.ApplyConditions", applyCondition, item); applyConditions = false; break; } @@ -1046,7 +1042,7 @@ namespace MediaBrowser.Model.Dlna { if (!ConditionProcessor.IsVideoConditionSatisfied(applyCondition, width, height, bitDepth, videoBitrate, videoProfile, videoLevel, videoFramerate, packetLength, timestamp, isAnamorphic, isInterlaced, refFrames, numVideoStreams, numAudioStreams, videoCodecTag, isAvc)) { - //LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource); + // LogConditionFailure(profile, "VideoCodecProfile.ApplyConditions", applyCondition, mediaSource); applyConditions = false; break; } @@ -1092,7 +1088,7 @@ namespace MediaBrowser.Model.Dlna { if (!ConditionProcessor.IsVideoAudioConditionSatisfied(applyCondition, audioChannels, audioBitrate, audioSampleRate, audioBitDepth, audioProfile, isSecondaryAudio)) { - //LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource); + // LogConditionFailure(profile, "VideoAudioCodecProfile.ApplyConditions", applyCondition, mediaSource); applyConditions = false; break; } diff --git a/MediaBrowser.Model/Entities/ImageType.cs b/MediaBrowser.Model/Entities/ImageType.cs index d89a4b3ad..6ea9ee419 100644 --- a/MediaBrowser.Model/Entities/ImageType.cs +++ b/MediaBrowser.Model/Entities/ImageType.cs @@ -63,6 +63,11 @@ namespace MediaBrowser.Model.Entities /// /// The box rear. /// - BoxRear = 11 + BoxRear = 11, + + /// + /// The user profile image. + /// + Profile = 12 } } diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index fa09cc513..3db72f78a 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -114,7 +114,7 @@ namespace MediaBrowser.Model.Entities { if (Type == MediaStreamType.Audio) { - //if (!string.IsNullOrEmpty(Title)) + // if (!string.IsNullOrEmpty(Title)) //{ // return AddLanguageIfNeeded(Title); //} @@ -208,7 +208,6 @@ namespace MediaBrowser.Model.Entities if (Type == MediaStreamType.Video) { - } return null; diff --git a/MediaBrowser.Model/Entities/PackageReviewInfo.cs b/MediaBrowser.Model/Entities/PackageReviewInfo.cs index 1ebbc3323..5b22b34ac 100644 --- a/MediaBrowser.Model/Entities/PackageReviewInfo.cs +++ b/MediaBrowser.Model/Entities/PackageReviewInfo.cs @@ -36,6 +36,5 @@ namespace MediaBrowser.Model.Entities /// Gets or sets the time of review. /// public DateTime timestamp { get; set; } - } } diff --git a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs index 19039d448..0b9b4141a 100644 --- a/MediaBrowser.Model/LiveTv/TimerInfoDto.cs +++ b/MediaBrowser.Model/LiveTv/TimerInfoDto.cs @@ -41,6 +41,5 @@ namespace MediaBrowser.Model.LiveTv /// /// The program information. public BaseItemDto ProgramInfo { get; set; } - } } diff --git a/MediaBrowser.Model/Net/HttpException.cs b/MediaBrowser.Model/Net/HttpException.cs index 4b15e30f0..48ff5d51c 100644 --- a/MediaBrowser.Model/Net/HttpException.cs +++ b/MediaBrowser.Model/Net/HttpException.cs @@ -28,7 +28,6 @@ namespace MediaBrowser.Model.Net public HttpException(string message, Exception innerException) : base(message, innerException) { - } /// diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs index 9e570ef1c..239a3777e 100644 --- a/MediaBrowser.Model/Notifications/NotificationOptions.cs +++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs @@ -2,7 +2,10 @@ #pragma warning disable CS1591 using System; +using Jellyfin.Data.Enums; +using MediaBrowser.Model.Extensions; using System.Linq; +using Jellyfin.Data.Entities; using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Notifications @@ -104,7 +107,7 @@ namespace MediaBrowser.Model.Notifications !opt.DisabledMonitorUsers.Contains(userId.ToString(""), StringComparer.OrdinalIgnoreCase); } - public bool IsEnabledToSendToUser(string type, string userId, UserPolicy userPolicy) + public bool IsEnabledToSendToUser(string type, string userId, User user) { NotificationOption opt = GetOptions(type); @@ -115,7 +118,7 @@ namespace MediaBrowser.Model.Notifications return true; } - if (opt.SendToUserMode == SendToUserType.Admins && userPolicy.IsAdministrator) + if (opt.SendToUserMode == SendToUserType.Admins && user.HasPermission(PermissionKind.IsAdministrator)) { return true; } diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs index c96eb0b59..989741c01 100644 --- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs +++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs @@ -51,6 +51,5 @@ namespace MediaBrowser.Model.Providers public RemoteSearchResult[] Artists { get; set; } - } } diff --git a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs index 89d245787..c749f7b13 100644 --- a/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs +++ b/MediaBrowser.Model/SyncPlay/GroupUpdateType.cs @@ -9,42 +9,52 @@ namespace MediaBrowser.Model.SyncPlay /// The user-joined update. Tells members of a group about a new user. /// UserJoined, + /// /// The user-left update. Tells members of a group that a user left. /// UserLeft, + /// /// The group-joined update. Tells a user that the group has been joined. /// GroupJoined, + /// /// The group-left update. Tells a user that the group has been left. /// GroupLeft, + /// /// The group-wait update. Tells members of the group that a user is buffering. /// GroupWait, + /// /// The prepare-session update. Tells a user to load some content. /// PrepareSession, + /// /// The not-in-group error. Tells a user that they don't belong to a group. /// NotInGroup, + /// /// The group-does-not-exist error. Sent when trying to join a non-existing group. /// GroupDoesNotExist, + /// /// The create-group-denied error. Sent when a user tries to create a group without required permissions. /// CreateGroupDenied, + /// /// The join-group-denied error. Sent when a user tries to join a group without required permissions. /// JoinGroupDenied, + /// /// The library-access-denied error. Sent when a user tries to join a group without required access to the library. /// diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs index f1e175fde..671f4e01f 100644 --- a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs +++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Model.SyncPlay { /// - /// Enum PlaybackRequestType + /// Enum PlaybackRequestType. /// public enum PlaybackRequestType { @@ -9,22 +9,27 @@ namespace MediaBrowser.Model.SyncPlay /// A user is requesting a play command for the group. /// Play = 0, + /// /// A user is requesting a pause command for the group. /// Pause = 1, + /// /// A user is requesting a seek command for the group. /// Seek = 2, + /// /// A user is signaling that playback is buffering. /// Buffering = 3, + /// /// A user is signaling that playback resumed. /// BufferingDone = 4, + /// /// A user is reporting its ping. /// diff --git a/MediaBrowser.Model/SyncPlay/SendCommandType.cs b/MediaBrowser.Model/SyncPlay/SendCommandType.cs index 113719871..86dec9e90 100644 --- a/MediaBrowser.Model/SyncPlay/SendCommandType.cs +++ b/MediaBrowser.Model/SyncPlay/SendCommandType.cs @@ -9,10 +9,12 @@ namespace MediaBrowser.Model.SyncPlay /// The play command. Instructs users to start playback. /// Play = 0, + /// /// The pause command. Instructs users to pause playback. /// Pause = 1, + /// /// The seek command. Instructs users to seek to a specified time. /// diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index e6c5cbe66..2fd27d3b9 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -2,7 +2,9 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Model.Configuration; +using System.Xml.Serialization; +using Jellyfin.Data.Enums; +using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule; namespace MediaBrowser.Model.Users { @@ -78,6 +80,8 @@ namespace MediaBrowser.Model.Users public string[] BlockedChannels { get; set; } public int RemoteClientBitrateLimit { get; set; } + + [XmlElement(ElementName = "AuthenticationProviderId")] public string AuthenticationProviderId { get; set; } public string PasswordResetProviderId { get; set; } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 3ab621ba4..3c94f6215 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -5,17 +5,21 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Person = MediaBrowser.Controller.Entities.Person; +using Season = MediaBrowser.Controller.Entities.TV.Season; namespace MediaBrowser.Providers.Manager { @@ -78,11 +82,6 @@ namespace MediaBrowser.Providers.Manager var saveLocally = item.SupportsLocalMetadata && item.IsSaveLocalMetadataEnabled() && !item.ExtraType.HasValue && !(item is Audio); - if (item is User) - { - saveLocally = true; - } - if (type != ImageType.Primary && item is Episode) { saveLocally = false; @@ -132,11 +131,11 @@ namespace MediaBrowser.Providers.Manager var currentImage = GetCurrentImage(item, type, index); var currentImageIsLocalFile = currentImage != null && currentImage.IsLocalFile; - var currentImagePath = currentImage == null ? null : currentImage.Path; + var currentImagePath = currentImage?.Path; var savedPaths = new List(); - using (source) + await using (source) { var currentPathIndex = 0; @@ -172,7 +171,6 @@ namespace MediaBrowser.Providers.Manager } catch (FileNotFoundException) { - } finally { @@ -181,6 +179,11 @@ namespace MediaBrowser.Providers.Manager } } + public async Task SaveImage(User user, Stream source, string path) + { + await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false); + } + private async Task SaveImageToLocation(Stream source, string path, string retryPath, CancellationToken cancellationToken) { try @@ -244,7 +247,7 @@ namespace MediaBrowser.Providers.Manager _fileSystem.SetAttributes(path, false, false); - using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) + await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous)) { await source.CopyToAsync(fs, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false); } @@ -439,7 +442,6 @@ namespace MediaBrowser.Providers.Manager { path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension); } - else if (item.IsInMixedFolder) { path = GetSavePathForItemInMixedFolder(item, type, filename, extension); diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 48e1c94ad..7901503d3 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -335,7 +335,6 @@ namespace MediaBrowser.Providers.Manager } catch (FileNotFoundException) { - } } @@ -506,7 +505,7 @@ namespace MediaBrowser.Providers.Manager return false; } - //if (!item.IsSaveLocalMetadataEnabled()) + // if (!item.IsSaveLocalMetadataEnabled()) //{ // return true; //} @@ -529,7 +528,6 @@ namespace MediaBrowser.Providers.Manager { Path = path, Type = imageType - }, newIndex); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 4ea711a2a..73fb63743 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Manager ApplySearchResult(id, refreshOptions.SearchResult); } - //await FindIdentities(id, cancellationToken).ConfigureAwait(false); + // await FindIdentities(id, cancellationToken).ConfigureAwait(false); id.IsAutomated = refreshOptions.IsAutomated; var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false); @@ -252,7 +252,7 @@ namespace MediaBrowser.Providers.Manager private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken) { - //if (libraryOptions.DownloadImagesInAdvance) + // if (libraryOptions.DownloadImagesInAdvance) //{ // try // { @@ -772,14 +772,14 @@ namespace MediaBrowser.Providers.Manager } } - //var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0; + // var isUnidentified = failedProviderCount > 0 && successfulProviderCount == 0; foreach (var provider in customProviders.Where(i => !(i is IPreRefreshProvider))) { await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false); } - //ImportUserData(item, userDataList, cancellationToken); + // ImportUserData(item, userDataList, cancellationToken); return refreshResult; } @@ -906,7 +906,7 @@ namespace MediaBrowser.Providers.Manager { var hasChanged = changeMonitor.HasChanged(item, directoryService); - //if (hasChanged) + // if (hasChanged) //{ // logger.LogDebug("{0} reports change to {1}", changeMonitor.GetType().Name, item.Path ?? item.Name); //} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index 13a4436e1..5853c7714 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Entities; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; @@ -16,7 +17,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Subtitles; @@ -28,6 +28,12 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; using Priority_Queue; +using Book = MediaBrowser.Controller.Entities.Book; +using Episode = MediaBrowser.Controller.Entities.TV.Episode; +using Movie = MediaBrowser.Controller.Entities.Movies.Movie; +using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum; +using Season = MediaBrowser.Controller.Entities.TV.Season; +using Series = MediaBrowser.Controller.Entities.TV.Series; namespace MediaBrowser.Providers.Manager { @@ -182,6 +188,12 @@ namespace MediaBrowser.Providers.Manager return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken); } + public Task SaveImage(User user, Stream source, string mimeType, string path) + { + return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger) + .SaveImage(user, source, path); + } + public async Task> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken) { var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders); @@ -819,7 +831,7 @@ namespace MediaBrowser.Providers.Manager } } - //_logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList)); + // _logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList)); return resultList; } @@ -893,7 +905,6 @@ namespace MediaBrowser.Providers.Manager i.UrlFormatString, value) }; - }).Where(i => i != null).Concat(item.GetRelatedUrls()); } diff --git a/MediaBrowser.Providers/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs index 7f2a1410b..60410032e 100644 --- a/MediaBrowser.Providers/Manager/ProviderUtils.cs +++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs @@ -111,7 +111,6 @@ namespace MediaBrowser.Providers.Manager if (replaceData || targetResult.People == null || targetResult.People.Count == 0) { targetResult.People = sourceResult.People; - } else if (targetResult.People != null && sourceResult.People != null) { diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 7023ef706..ba87e0570 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -79,7 +79,6 @@ namespace MediaBrowser.Providers.MediaInfo } catch { - } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 3b6c8ff72..73c89e815 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -63,7 +63,6 @@ namespace MediaBrowser.Providers.MediaInfo Path = path, Protocol = protocol } - }, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); @@ -91,8 +90,8 @@ namespace MediaBrowser.Providers.MediaInfo audio.RunTimeTicks = mediaInfo.RunTimeTicks; audio.Size = mediaInfo.Size; - //var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.'); - //audio.Container = extension; + // var extension = (Path.GetExtension(audio.Path) ?? string.Empty).TrimStart('.'); + // audio.Container = extension; FetchDataFromTags(audio, mediaInfo); diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 933cf03d6..ccbe27c1f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -176,7 +176,7 @@ namespace MediaBrowser.Providers.MediaInfo mediaAttachments = mediaInfo.MediaAttachments; video.TotalBitrate = mediaInfo.Bitrate; - //video.FormatName = (mediaInfo.Container ?? string.Empty) + // video.FormatName = (mediaInfo.Container ?? string.Empty) // .Replace("matroska", "mkv", StringComparison.OrdinalIgnoreCase); // For dvd's this may not always be accurate, so don't set the runtime if the item already has one @@ -283,7 +283,7 @@ namespace MediaBrowser.Providers.MediaInfo { var video = (Video)item; - //video.PlayableStreamFileNames = blurayInfo.Files.ToList(); + // video.PlayableStreamFileNames = blurayInfo.Files.ToList(); // Use BD Info if it has multiple m2ts. Otherwise, treat it like a video file and rely more on ffprobe output if (blurayInfo.Files.Length > 1) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index 2bbe8a968..1477488d7 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -60,7 +60,6 @@ namespace MediaBrowser.Providers.MediaInfo } catch (IOException) { - } return streams; @@ -189,9 +188,9 @@ namespace MediaBrowser.Providers.MediaInfo filename = filename.Replace(" ", string.Empty); // can't normalize this due to languages such as pt-br - //filename = filename.Replace("-", string.Empty); + // filename = filename.Replace("-", string.Empty); - //filename = filename.Replace(".", string.Empty); + // filename = filename.Replace(".", string.Empty); return filename; } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index d8a18a6bc..fdba779be 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -85,7 +85,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb private void ProcessResult(MusicArtist item, Artist result, string preferredLanguage) { - //item.HomePageUrl = result.strWebsite; + // item.HomePageUrl = result.strWebsite; if (!string.IsNullOrEmpty(result.strGenre)) { diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 19b4bd1e3..fe5f0e9bf 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -77,7 +77,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) && voteCount >= 0) { - //item.VoteCount = voteCount; + // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) @@ -178,7 +178,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) && voteCount >= 0) { - //item.VoteCount = voteCount; + // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs index c9edcc8e9..30de1391d 100644 --- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs @@ -247,7 +247,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb ProductionYear = firstAired.Year, SearchProviderName = Name, ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner - }; try diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs index 60f37dc17..2c6e08921 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/GenericTmdbMovieInfo.cs @@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.Overview = string.IsNullOrWhiteSpace(movieData.Overview) ? null : WebUtility.HtmlDecode(movieData.Overview); movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null; - //movie.HomePageUrl = movieData.homepage; + // movie.HomePageUrl = movieData.homepage; if (!string.IsNullOrEmpty(movieData.Tagline)) { @@ -167,7 +167,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.CommunityRating = rating; } - //movie.VoteCount = movieData.vote_count; + // movie.VoteCount = movieData.vote_count; if (movieData.Releases != null && movieData.Releases.Countries != null) { @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - //studios + // studios if (movieData.Production_Companies != null) { movie.SetStudios(movieData.Production_Companies.Select(c => c.Name)); @@ -219,8 +219,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies resultItem.ResetPeople(); var tmdbImageUrl = settings.images.GetImageUrl("original"); - //Actors, Directors, Writers - all in People - //actors come from cast + // Actors, Directors, Writers - all in People + // actors come from cast if (movieData.Casts != null && movieData.Casts.Cast != null) { foreach (var actor in movieData.Casts.Cast.OrderBy(a => a.Order)) @@ -247,7 +247,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - //and the rest from crew + // and the rest from crew if (movieData.Casts?.Crew != null) { var keepTypes = new[] @@ -289,7 +289,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - //if (movieData.keywords != null && movieData.keywords.keywords != null) + // if (movieData.keywords != null && movieData.keywords.keywords != null) //{ // movie.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList(); //} @@ -304,6 +304,5 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies }).ToArray(); } } - } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 64d3ecd7b..faeb48b12 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -149,7 +149,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies Url = string.Format(TmdbConfigUrl, TmdbUtils.ApiKey), CancellationToken = cancellationToken, AcceptHeader = TmdbUtils.AcceptHeader - }).ConfigureAwait(false)) { using (Stream json = response.Content) @@ -344,7 +343,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies AcceptHeader = TmdbUtils.AcceptHeader, CacheMode = cacheMode, CacheLength = cacheLength - }).ConfigureAwait(false)) { using (var json = response.Content) @@ -389,7 +387,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies AcceptHeader = TmdbUtils.AcceptHeader, CacheMode = cacheMode, CacheLength = cacheLength - }).ConfigureAwait(false)) { using (var json = response.Content) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs index e1e34afb9..c5dc060c0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbSearch.cs @@ -94,7 +94,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (results.Count == 0) { - //try in english if wasn't before + // try in english if wasn't before if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { results = await GetSearchResults(name, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false); @@ -128,7 +128,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase)) { - //one more time, in english + // one more time, in english results = await GetSearchResults(name2, searchType, year, "en", tmdbImageUrl, cancellationToken).ConfigureAwait(false); } } @@ -203,7 +203,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture)); return remoteResult; - }) .ToList(); } @@ -224,7 +223,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies Url = url3, CancellationToken = cancellationToken, AcceptHeader = TmdbUtils.AcceptHeader - }).ConfigureAwait(false)) { using (var json = response.Content) @@ -256,7 +254,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies remoteResult.SetProviderId(MetadataProvider.Tmdb, i.Id.ToString(_usCulture)); return remoteResult; - }) .ToList(); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 3f28483f7..654e42a90 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People // TODO: This should go in PersonMetadataService, not each person provider item.Name = id.Name; - //item.HomePageUrl = info.homepage; + // item.HomePageUrl = info.homepage; if (!string.IsNullOrWhiteSpace(info.Place_Of_Birth)) { @@ -232,7 +232,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People Url = url, CancellationToken = cancellationToken, AcceptHeader = TmdbUtils.AcceptHeader - }).ConfigureAwait(false)) { using (var json = response.Content) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index 01b295f86..b4d092c89 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -141,8 +141,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var credits = response.Credits; if (credits != null) { - //Actors, Directors, Writers - all in People - //actors come from cast + // Actors, Directors, Writers - all in People + // actors come from cast if (credits.Cast != null) { foreach (var actor in credits.Cast.OrderBy(a => a.Order)) @@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - //and the rest from crew + // and the rest from crew if (credits.Crew != null) { var keepTypes = new[] diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs index f82f5f2ab..ab1f9dab5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProviderBase.cs @@ -127,7 +127,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV Url = url, CancellationToken = cancellationToken, AcceptHeader = TmdbUtils.AcceptHeader - }).ConfigureAwait(false)) { using (var json = response.Content) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index c7cd672b4..2cc82d5f6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.Item = new Season(); // Don't use moviedb season names for now until if/when we have field-level configuration - //result.Item.Name = seasonInfo.name; + // result.Item.Name = seasonInfo.name; result.Item.Name = info.Name; @@ -79,17 +79,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var credits = seasonInfo.Credits; if (credits != null) { - //Actors, Directors, Writers - all in People - //actors come from cast + // Actors, Directors, Writers - all in People + // actors come from cast if (credits.Cast != null) { - //foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order }); + // foreach (var actor in credits.cast.OrderBy(a => a.order)) result.Item.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order }); } - //and the rest from crew + // and the rest from crew if (credits.Crew != null) { - //foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); + // foreach (var person in credits.crew) result.Item.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department }); } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index cbef27a09..e92e5ceab 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -201,6 +201,5 @@ namespace MediaBrowser.Providers.Studios } } } - } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index c6ffc460c..8086533eb 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -311,7 +311,6 @@ namespace MediaBrowser.Providers.Subtitles Index = index, ItemId = item.Id, Type = MediaStreamType.Subtitle - }).First(); var path = stream.Path; @@ -365,9 +364,7 @@ namespace MediaBrowser.Providers.Subtitles { Name = i.Name, Id = GetProviderId(i.Name) - }).ToArray(); } - } } diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs index 6a1e6df8f..6e0511971 100644 --- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs +++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs @@ -40,11 +40,11 @@ namespace MediaBrowser.Providers.TV if (hasNewSeasons) { - //var directoryService = new DirectoryService(_fileSystem); + // var directoryService = new DirectoryService(_fileSystem); - //await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false); + // await series.RefreshMetadata(new MetadataRefreshOptions(directoryService), cancellationToken).ConfigureAwait(false); - //await series.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(directoryService)) + // await series.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(directoryService)) // .ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index bd59606e7..ec7873eaa 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -33,7 +33,6 @@ namespace MediaBrowser.Providers.TV /// public bool Supports(IHasProviderIds item) => item is Series; - } public class TvdbSeasonExternalId : IExternalId diff --git a/MediaBrowser.Providers/Users/UserMetadataService.cs b/MediaBrowser.Providers/Users/UserMetadataService.cs deleted file mode 100644 index ad4d315f5..000000000 --- a/MediaBrowser.Providers/Users/UserMetadataService.cs +++ /dev/null @@ -1,30 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Users -{ - public class UserMetadataService : MetadataService - { - public UserMetadataService( - IServerConfigurationManager serverConfigurationManager, - ILogger logger, - IProviderManager providerManager, - IFileSystem fileSystem, - ILibraryManager libraryManager) - : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager) - { - } - - /// - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - } -} diff --git a/RSSDP/DeviceAvailableEventArgs.cs b/RSSDP/DeviceAvailableEventArgs.cs index 21ac7c631..439cee467 100644 --- a/RSSDP/DeviceAvailableEventArgs.cs +++ b/RSSDP/DeviceAvailableEventArgs.cs @@ -54,6 +54,5 @@ namespace Rssdp } #endregion - } } diff --git a/RSSDP/DeviceEventArgs.cs b/RSSDP/DeviceEventArgs.cs index 05eb4a256..faeff8347 100644 --- a/RSSDP/DeviceEventArgs.cs +++ b/RSSDP/DeviceEventArgs.cs @@ -41,6 +41,5 @@ namespace Rssdp } #endregion - } } diff --git a/RSSDP/HttpParserBase.cs b/RSSDP/HttpParserBase.cs index 773a06cdb..c2eb0bf92 100644 --- a/RSSDP/HttpParserBase.cs +++ b/RSSDP/HttpParserBase.cs @@ -46,7 +46,7 @@ namespace Rssdp.Infrastructure { var lines = data.Split(LineTerminators, StringSplitOptions.None); - //First line is the 'request' line containing http protocol details like method, uri, http version etc. + // First line is the 'request' line containing http protocol details like method, uri, http version etc. ParseStatusLine(lines[0], message); ParseHeaders(headers, retVal.Headers, lines); @@ -93,16 +93,16 @@ namespace Rssdp.Infrastructure /// A reference to a collection for the message content, to which the parsed header will be added. private void ParseHeader(string line, System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders) { - //Header format is - //name: value + // Header format is + // name: value var headerKeySeparatorIndex = line.IndexOf(":", StringComparison.OrdinalIgnoreCase); var headerName = line.Substring(0, headerKeySeparatorIndex).Trim(); var headerValue = line.Substring(headerKeySeparatorIndex + 1).Trim(); - //Not sure how to determine where request headers and and content headers begin, - //at least not without a known set of headers (general headers first the content headers) - //which seems like a bad way of doing it. So we'll assume if it's a known content header put it there - //else use request headers. + // Not sure how to determine where request headers and and content headers begin, + // at least not without a known set of headers (general headers first the content headers) + // which seems like a bad way of doing it. So we'll assume if it's a known content header put it there + // else use request headers. var values = ParseValues(headerValue); var headersToAddTo = IsContentHeader(headerName) ? contentHeaders : headers; @@ -115,13 +115,13 @@ namespace Rssdp.Infrastructure private int ParseHeaders(System.Net.Http.Headers.HttpHeaders headers, System.Net.Http.Headers.HttpHeaders contentHeaders, string[] lines) { - //Blank line separates headers from content, so read headers until we find blank line. + // Blank line separates headers from content, so read headers until we find blank line. int lineIndex = 1; string line = null, nextLine = null; while (lineIndex + 1 < lines.Length && !String.IsNullOrEmpty((line = lines[lineIndex++]))) { - //If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability. - //Combine these lines into a single comma separated style header for easier parsing. + // If the following line starts with space or tab (or any whitespace), it is really part of this header but split for human readability. + // Combine these lines into a single comma separated style header for easier parsing. while (lineIndex < lines.Length && !String.IsNullOrEmpty((nextLine = lines[lineIndex]))) { if (nextLine.Length > 0 && Char.IsWhiteSpace(nextLine[0])) diff --git a/RSSDP/HttpRequestParser.cs b/RSSDP/HttpRequestParser.cs index 279ef883c..3fc328b8c 100644 --- a/RSSDP/HttpRequestParser.cs +++ b/RSSDP/HttpRequestParser.cs @@ -85,6 +85,5 @@ namespace Rssdp.Infrastructure } #endregion - } } diff --git a/RSSDP/ISsdpCommunicationsServer.cs b/RSSDP/ISsdpCommunicationsServer.cs index 8cf65df11..02c3af0e5 100644 --- a/RSSDP/ISsdpCommunicationsServer.cs +++ b/RSSDP/ISsdpCommunicationsServer.cs @@ -61,6 +61,5 @@ namespace Rssdp.Infrastructure bool IsShared { get; set; } #endregion - } } diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 18097ef24..863f2b15c 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -195,11 +195,9 @@ namespace Rssdp.Infrastructure } catch (ObjectDisposedException) { - } catch (OperationCanceledException) { - } catch (Exception ex) { @@ -485,9 +483,9 @@ namespace Rssdp.Infrastructure private void OnRequestReceived(HttpRequestMessage data, IPEndPoint remoteEndPoint, IPAddress receivedOnLocalIpAddress) { - //SSDP specification says only * is currently used but other uri's might - //be implemented in the future and should be ignored unless understood. - //Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11 + // SSDP specification says only * is currently used but other uri's might + // be implemented in the future and should be ignored unless understood. + // Section 4.2 - http://tools.ietf.org/html/draft-cai-ssdp-v1-03#page-11 if (data.RequestUri.ToString() != "*") { return; diff --git a/RSSDP/SsdpConstants.cs b/RSSDP/SsdpConstants.cs index 28a014fce..798f050e1 100644 --- a/RSSDP/SsdpConstants.cs +++ b/RSSDP/SsdpConstants.cs @@ -57,6 +57,5 @@ namespace Rssdp.Infrastructure internal const string SsdpByeByeNotification = "ssdp:byebye"; internal const int UdpResendCount = 3; - } } diff --git a/RSSDP/SsdpDevice.cs b/RSSDP/SsdpDevice.cs index 09f729e83..691ba2a14 100644 --- a/RSSDP/SsdpDevice.cs +++ b/RSSDP/SsdpDevice.cs @@ -349,6 +349,5 @@ namespace Rssdp } #endregion - } } diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 59a2710d5..a626e13b9 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -120,7 +120,6 @@ namespace Rssdp.Infrastructure } catch (Exception) { - } } @@ -337,7 +336,7 @@ namespace Rssdp.Infrastructure values["HOST"] = "239.255.255.250:1900"; values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2"; - //values["X-EMBY-SERVERID"] = _appHost.SystemId; + // values["X-EMBY-SERVERID"] = _appHost.SystemId; values["MAN"] = "\"ssdp:discover\""; @@ -613,6 +612,5 @@ namespace Rssdp.Infrastructure } #endregion - } } diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 53b740052..5dfb6a8c2 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -205,25 +205,25 @@ namespace Rssdp.Infrastructure return; } - //WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget)); + // WriteTrace(String.Format("Search Request Received From {0}, Target = {1}", remoteEndPoint.ToString(), searchTarget)); if (IsDuplicateSearchRequest(searchTarget, remoteEndPoint)) { - //WriteTrace("Search Request is Duplicate, ignoring."); + // WriteTrace("Search Request is Duplicate, ignoring."); return; } - //Wait on random interval up to MX, as per SSDP spec. - //Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120. - //Using 16 as minimum as that's often the minimum system clock frequency anyway. + // Wait on random interval up to MX, as per SSDP spec. + // Also, as per UPnP 1.1/SSDP spec ignore missing/bank MX header. If over 120, assume random value between 0 and 120. + // Using 16 as minimum as that's often the minimum system clock frequency anyway. int maxWaitInterval = 0; if (String.IsNullOrEmpty(mx)) { - //Windows Explorer is poorly behaved and doesn't supply an MX header value. - //if (this.SupportPnpRootDevice) + // Windows Explorer is poorly behaved and doesn't supply an MX header value. + // if (this.SupportPnpRootDevice) mx = "1"; - //else - //return; + // else + // return; } if (!Int32.TryParse(mx, out maxWaitInterval) || maxWaitInterval <= 0) return; @@ -231,10 +231,10 @@ namespace Rssdp.Infrastructure if (maxWaitInterval > 120) maxWaitInterval = _Random.Next(0, 120); - //Do not block synchronously as that may tie up a threadpool thread for several seconds. + // Do not block synchronously as that may tie up a threadpool thread for several seconds. Task.Delay(_Random.Next(16, (maxWaitInterval * 1000))).ContinueWith((parentTask) => { - //Copying devices to local array here to avoid threading issues/enumerator exceptions. + // Copying devices to local array here to avoid threading issues/enumerator exceptions. IEnumerable devices = null; lock (_Devices) { @@ -251,7 +251,7 @@ namespace Rssdp.Infrastructure if (devices != null) { var deviceList = devices.ToList(); - //WriteTrace(String.Format("Sending {0} search responses", deviceList.Count)); + // WriteTrace(String.Format("Sending {0} search responses", deviceList.Count)); foreach (var device in deviceList) { @@ -264,7 +264,7 @@ namespace Rssdp.Infrastructure } else { - //WriteTrace(String.Format("Sending 0 search responses.")); + // WriteTrace(String.Format("Sending 0 search responses.")); } }); } @@ -308,7 +308,7 @@ namespace Rssdp.Infrastructure { var rootDevice = device.ToRootDevice(); - //var additionalheaders = FormatCustomHeadersForResponse(device); + // var additionalheaders = FormatCustomHeadersForResponse(device); const string header = "HTTP/1.1 200 OK"; @@ -335,10 +335,9 @@ namespace Rssdp.Infrastructure } catch (Exception) { - } - //WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); + // WriteTrace(String.Format("Sent search response to " + endPoint.ToString()), device); } private bool IsDuplicateSearchRequest(string searchTarget, IPEndPoint endPoint) @@ -384,7 +383,7 @@ namespace Rssdp.Infrastructure { if (IsDisposed) return; - //WriteTrace("Begin Sending Alive Notifications For All Devices"); + // WriteTrace("Begin Sending Alive Notifications For All Devices"); SsdpRootDevice[] devices; lock (_Devices) @@ -399,7 +398,7 @@ namespace Rssdp.Infrastructure SendAliveNotifications(device, true, CancellationToken.None); } - //WriteTrace("Completed Sending Alive Notifications For All Devices"); + // WriteTrace("Completed Sending Alive Notifications For All Devices"); } catch (ObjectDisposedException ex) { @@ -448,7 +447,7 @@ namespace Rssdp.Infrastructure _CommsServer.SendMulticastMessage(message, _sendOnlyMatchedHost ? rootDevice.Address : null, cancellationToken); - //WriteTrace(String.Format("Sent alive notification"), device); + // WriteTrace(String.Format("Sent alive notification"), device); } private Task SendByeByeNotifications(SsdpDevice device, bool isRoot, CancellationToken cancellationToken) @@ -533,7 +532,7 @@ namespace Rssdp.Infrastructure { LogFunction(text); } - //System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher"); + // System.Diagnostics.Debug.WriteLine(text, "SSDP Publisher"); } private void WriteTrace(string text, SsdpDevice device) @@ -551,13 +550,13 @@ namespace Rssdp.Infrastructure if (string.Equals(e.Message.Method.Method, SsdpConstants.MSearchMethod, StringComparison.OrdinalIgnoreCase)) { - //According to SSDP/UPnP spec, ignore message if missing these headers. + // According to SSDP/UPnP spec, ignore message if missing these headers. // Edit: But some devices do it anyway - //if (!e.Message.Headers.Contains("MX")) + // if (!e.Message.Headers.Contains("MX")) // WriteTrace("Ignoring search request - missing MX header."); - //else if (!e.Message.Headers.Contains("MAN")) + // else if (!e.Message.Headers.Contains("MAN")) // WriteTrace("Ignoring search request - missing MAN header."); - //else + // else ProcessSearchRequest(GetFirstHeaderValue(e.Message.Headers, "MX"), GetFirstHeaderValue(e.Message.Headers, "ST"), e.ReceivedFrom, e.LocalIpAddress, CancellationToken.None); } } diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs index 3b3d03c8b..4245c3249 100644 --- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs +++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs @@ -7,6 +7,8 @@ using AutoFixture; using AutoFixture.AutoMoq; using Jellyfin.Api.Auth; using Jellyfin.Api.Constants; +using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; @@ -124,7 +126,7 @@ namespace Jellyfin.Api.Tests.Auth var user = SetupUser(); var authenticateResult = await _sut.AuthenticateAsync(); - Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Name)); + Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Name, user.Username)); } [Theory] @@ -135,7 +137,7 @@ namespace Jellyfin.Api.Tests.Auth var user = SetupUser(isAdmin); var authenticateResult = await _sut.AuthenticateAsync(); - var expectedRole = user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User; + var expectedRole = user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User; Assert.True(authenticateResult.Principal.HasClaim(ClaimTypes.Role, expectedRole)); } @@ -151,7 +153,7 @@ namespace Jellyfin.Api.Tests.Auth private User SetupUser(bool isAdmin = false) { var user = _fixture.Create(); - user.Policy.IsAdministrator = isAdmin; + user.SetPermission(PermissionKind.IsAdministrator, isAdmin); _jellyfinAuthServiceMock.Setup( a => a.Authenticate( diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 269ff0986..aedcc7c42 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -24,9 +24,21 @@ + + + + + + + + + + ../jellyfin-tests.ruleset + + diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj index 003e29a66..73af10065 100644 --- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj +++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj @@ -19,8 +19,20 @@ + + + + + + + + + + ../jellyfin-tests.ruleset + + diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs index 03523dbc4..46926f4f8 100644 --- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs +++ b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs @@ -7,7 +7,8 @@ namespace Jellyfin.Common.Tests public class PasswordHashTests { [Theory] - [InlineData("$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", + [InlineData( + "$PBKDF2$iterations=1000$62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D", "PBKDF2", "", "62FBA410AFCA5B4475F35137AB2E8596B127E4D927BA23F6CC05C067E897042D")] diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj index 3c806be17..5c7934fe3 100644 --- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj +++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj @@ -19,8 +19,20 @@ + + + + + + + + + + ../jellyfin-tests.ruleset + + diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs index e0f1f236c..af29fec87 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs @@ -9,20 +9,6 @@ namespace Jellyfin.MediaEncoding.Tests { public class EncoderValidatorTests { - private class GetFFmpegVersionTestData : IEnumerable - { - public IEnumerator GetEnumerator() - { - yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) }; - yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) }; - yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) }; - yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) }; - yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null }; - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - [Theory] [ClassData(typeof(GetFFmpegVersionTestData))] public void GetFFmpegVersionTest(string versionOutput, Version? version) @@ -41,5 +27,19 @@ namespace Jellyfin.MediaEncoding.Tests var val = new EncoderValidator(new NullLogger()); Assert.Equal(valid, val.ValidateVersionInternal(versionOutput)); } + + private class GetFFmpegVersionTestData : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) }; + yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) }; + yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) }; + yield return new object?[] { EncoderValidatorTestsData.FFmpegV404Output, new Version(4, 0, 4) }; + yield return new object?[] { EncoderValidatorTestsData.FFmpegGitUnknownOutput, null }; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } } } diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj index c2e7bf54c..74fc37e49 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj +++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj @@ -25,8 +25,20 @@ + + + + + + + + + + ../jellyfin-tests.ruleset + + diff --git a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj index f6c327498..e93385136 100644 --- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj +++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj @@ -14,8 +14,20 @@ + + + + + + + + + + ../jellyfin-tests.ruleset + + diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj index 43b34dcee..355949640 100644 --- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj +++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj @@ -9,6 +9,7 @@ netcoreapp3.1 false enable + true @@ -21,11 +22,13 @@ - + + + diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 3bad6bb7b..83aca38b4 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -5,25 +5,37 @@ {2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} - - netcoreapp3.1 - false - true - enable - Jellyfin.Server.Implementations.Tests - + + netcoreapp3.1 + false + true + enable + Jellyfin.Server.Implementations.Tests + - - - - - - - - + + + + + + + + - - - + + + + + + + + + + + + + + ../jellyfin-tests.ruleset + diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj index c845ca6cf..597053131 100644 --- a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj +++ b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj @@ -20,10 +20,12 @@ - + + +