aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Dlna/ContentDirectory/ContentDirectory.cs8
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs33
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs14
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs10
-rw-r--r--Emby.Drawing/ImageProcessor.cs9
-rw-r--r--Emby.Notifications/Api/NotificationsService.cs10
-rw-r--r--Emby.Notifications/NotificationManager.cs8
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs56
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs10
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs11
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteDisplayPreferencesRepository.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs1
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserRepository.cs240
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs29
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs16
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs1
-rw-r--r--Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs3
-rw-r--r--Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs77
-rw-r--r--Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs28
-rw-r--r--Emby.Server.Implementations/HttpServer/ResponseFilter.cs1
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs22
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs9
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs47
-rw-r--r--Emby.Server.Implementations/Library/MediaStreamSelector.cs2
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs18
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs3
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs1107
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs30
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs25
-rw-r--r--Emby.Server.Implementations/Playlists/ManualPlaylistsFolder.cs3
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs3
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs82
-rw-r--r--Emby.Server.Implementations/Sorting/DateLastMediaAddedComparer.cs1
-rw-r--r--Emby.Server.Implementations/Sorting/DatePlayedComparer.cs1
-rw-r--r--Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs1
-rw-r--r--Emby.Server.Implementations/Sorting/IsPlayedComparer.cs1
-rw-r--r--Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs1
-rw-r--r--Emby.Server.Implementations/Sorting/PlayCountComparer.cs1
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs79
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs14
-rw-r--r--Jellyfin.Api/Auth/CustomAuthenticationHandler.cs5
-rw-r--r--Jellyfin.Api/Controllers/StartupController.cs8
-rw-r--r--Jellyfin.Data/DayOfWeekHelper.cs65
-rw-r--r--Jellyfin.Data/Entities/AccessSchedule.cs91
-rw-r--r--Jellyfin.Data/Entities/Group.cs103
-rw-r--r--Jellyfin.Data/Entities/ImageInfo.cs30
-rw-r--r--Jellyfin.Data/Entities/Permission.cs137
-rw-r--r--Jellyfin.Data/Entities/Preference.cs100
-rw-r--r--Jellyfin.Data/Entities/ProviderMapping.cs6
-rw-r--r--Jellyfin.Data/Entities/Series.cs20
-rw-r--r--Jellyfin.Data/Entities/User.cs500
-rw-r--r--Jellyfin.Data/Enums/DynamicDayOfWeek.cs (renamed from MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs)2
-rw-r--r--Jellyfin.Data/Enums/PermissionKind.cs127
-rw-r--r--Jellyfin.Data/Enums/PreferenceKind.cs69
-rw-r--r--Jellyfin.Data/Enums/SubtitlePlaybackMode.cs (renamed from MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs)4
-rw-r--r--Jellyfin.Data/Enums/SyncPlayAccess.cs (renamed from MediaBrowser.Model/Configuration/SyncplayAccess.cs)8
-rw-r--r--Jellyfin.Data/Enums/UnratedItem.cs (renamed from MediaBrowser.Model/Configuration/UnratedItem.cs)2
-rw-r--r--Jellyfin.Data/Enums/Weekday.cs13
-rw-r--r--Jellyfin.Data/IHasPermissions.cs31
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj1
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj2
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs70
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDbProvider.cs2
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.Designer.cs312
-rw-r--r--Jellyfin.Server.Implementations/Migrations/20200613202153_AddUsers.cs197
-rw-r--r--Jellyfin.Server.Implementations/Migrations/JellyfinDbModelSnapshot.cs244
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultAuthenticationProvider.cs (renamed from Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs)16
-rw-r--r--Jellyfin.Server.Implementations/Users/DefaultPasswordResetProvider.cs (renamed from Emby.Server.Implementations/Library/DefaultPasswordResetProvider.cs)59
-rw-r--r--Jellyfin.Server.Implementations/Users/DeviceAccessEntryPoint.cs67
-rw-r--r--Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs (renamed from Emby.Server.Implementations/Library/InvalidAuthProvider.cs)12
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs841
-rw-r--r--Jellyfin.Server/CoreAppHost.cs13
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs208
-rw-r--r--MediaBrowser.Api/BaseApiService.cs5
-rw-r--r--MediaBrowser.Api/FilterService.cs1
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs160
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs12
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs3
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs11
-rw-r--r--MediaBrowser.Api/Music/InstantMixService.cs1
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs3
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs36
-rw-r--r--MediaBrowser.Api/Sessions/SessionService.cs5
-rw-r--r--MediaBrowser.Api/SuggestionsService.cs1
-rw-r--r--MediaBrowser.Api/TvShowsService.cs8
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs1
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs19
-rw-r--r--MediaBrowser.Api/UserLibrary/PlaystateService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs2
-rw-r--r--MediaBrowser.Api/UserService.cs37
-rw-r--r--MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs2
-rw-r--r--MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs2
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs10
-rw-r--r--MediaBrowser.Controller/Collections/ICollectionManager.cs1
-rw-r--r--MediaBrowser.Controller/Devices/IDeviceManager.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs3
-rw-r--r--MediaBrowser.Controller/Drawing/ImageHelper.cs1
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs16
-rw-r--r--MediaBrowser.Controller/Entities/AudioBook.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs48
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs2
-rw-r--r--MediaBrowser.Controller/Entities/DayOfWeekHelper.cs71
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs28
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs14
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs2
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs2
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs10
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs29
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs2
-rw-r--r--MediaBrowser.Controller/Entities/User.cs262
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs1
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs19
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs53
-rw-r--r--MediaBrowser.Controller/Library/IIntroProvider.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs11
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs1
-rw-r--r--MediaBrowser.Controller/Library/IMusicManager.cs1
-rw-r--r--MediaBrowser.Controller/Library/IUserDataManager.cs3
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs130
-rw-r--r--MediaBrowser.Controller/Library/PlaybackProgressEventArgs.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs1
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs5
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs17
-rw-r--r--MediaBrowser.Controller/Net/AuthorizationInfo.cs10
-rw-r--r--MediaBrowser.Controller/Net/IAuthService.cs3
-rw-r--r--MediaBrowser.Controller/Net/ISessionContext.cs2
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationService.cs2
-rw-r--r--MediaBrowser.Controller/Notifications/UserNotification.cs2
-rw-r--r--MediaBrowser.Controller/Persistence/IUserRepository.cs27
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs11
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs3
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs3
-rw-r--r--MediaBrowser.Controller/Sorting/IUserBaseItemComparer.cs3
-rw-r--r--MediaBrowser.Model/Configuration/AccessSchedule.cs2
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs1
-rw-r--r--MediaBrowser.Model/Entities/ImageType.cs7
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOptions.cs7
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs24
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs14
-rw-r--r--MediaBrowser.Providers/Users/UserMetadataService.cs30
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs8
155 files changed, 3770 insertions, 2877 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index ce956176e..1bba7a51d 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)
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..27585eafa 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
{
@@ -731,7 +737,7 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var array = new ServerItem[]
+ var array = new[]
{
new ServerItem(item)
{
@@ -1115,7 +1121,7 @@ namespace Emby.Dlna.ContentDirectory
private QueryResult<ServerItem> 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..6cedb3ef0 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
{
@@ -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))
@@ -995,7 +1000,6 @@ namespace Emby.Dlna.Didl
}
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
-
}
private void AddImageResElement(
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.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
});
}
+ /// <inheritdoc />
+ 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<MediaBrowser.Controller.Entities.User> e)
+ private async void OnUserLockedOut(object sender, GenericEventArgs<User> 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<MediaBrowser.Controller.Entities.User> 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<MediaBrowser.Controller.Entities.User> e)
+ private async void OnUserDeleted(object sender, GenericEventArgs<User> 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<MediaBrowser.Controller.Entities.User> e)
+ private async void OnUserPasswordChanged(object sender, GenericEventArgs<User> 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<MediaBrowser.Controller.Entities.User> e)
+ private async void OnUserCreated(object sender, GenericEventArgs<User> 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;
}
/// <summary>
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<IAuthenticationRepository, AuthenticationRepository>();
- serviceCollection.AddSingleton<IUserRepository, SqliteUserRepository>();
-
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
- serviceCollection.AddSingleton<IUserManager, UserManager>();
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
@@ -659,15 +656,11 @@ namespace Emby.Server.Implementations
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
- ((SqliteUserRepository)Resolve<IUserRepository>()).Initialize();
SetStaticProperties();
- var userManager = (UserManager)Resolve<IUserManager>();
- userManager.Initialize();
-
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
- ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, userManager);
+ ((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
FindParts();
}
@@ -750,7 +743,6 @@ namespace Emby.Server.Implementations
BaseItem.ProviderManager = Resolve<IProviderManager>();
BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
BaseItem.ItemRepository = Resolve<IItemRepository>();
- User.UserManager = Resolve<IUserManager>();
BaseItem.FileSystem = _fileSystemManager;
BaseItem.UserDataManager = Resolve<IUserDataManager>();
BaseItem.ChannelManager = Resolve<IChannelManager>();
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index f9eea66b4..519c5bbef 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<ChannelItemResult> GetChannelItems(IChannel channel,
+ private async Task<ChannelItemResult> GetChannelItems(
+ IChannel channel,
User user,
string externalFolderId,
ChannelItemSortField? sortField,
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/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/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 6ee6230fc..b99b74ef8 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;
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
-{
- /// <summary>
- /// Class SQLiteUserRepository
- /// </summary>
- public class SqliteUserRepository : BaseSqliteRepository, IUserRepository
- {
- private readonly JsonSerializerOptions _jsonOptions;
-
- public SqliteUserRepository(
- ILogger<SqliteUserRepository> logger,
- IServerApplicationPaths appPaths)
- : base(logger)
- {
- _jsonOptions = JsonDefaults.GetOptions();
-
- DbFilePath = Path.Combine(appPaths.DataPath, "users.db");
- }
-
- /// <summary>
- /// Gets the name of the repository
- /// </summary>
- /// <value>The name.</value>
- public string Name => "SQLite";
-
- /// <summary>
- /// Opens the connection to the database.
- /// </summary>
- 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);
- }
- }
-
- /// <summary>
- /// Save a user in the repo
- /// </summary>
- 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<IResultSetValue> row)
- {
- var id = row[0].ToInt64();
- var guid = row[1].ReadGuidFromBlob();
-
- var user = JsonSerializer.Deserialize<User>(row[2].ToBlob(), _jsonOptions);
- user.InternalId = id;
- user.Id = guid;
- return user;
- }
-
- /// <summary>
- /// Retrieve all users from the database
- /// </summary>
- /// <returns>IEnumerable{User}.</returns>
- public List<User> RetrieveAllUsers()
- {
- using (var connection = GetConnection(true))
- {
- return new List<User>(RetrieveAllUsers(connection));
- }
- }
-
- /// <summary>
- /// Retrieve all users from the database
- /// </summary>
- /// <returns>IEnumerable{User}.</returns>
- private IEnumerable<User> RetrieveAllUsers(ManagedConnection connection)
- {
- foreach (var row in connection.Query("select id,guid,data from LocalUsersv2"))
- {
- yield return GetUser(row);
- }
- }
-
- /// <summary>
- /// Deletes the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">user</exception>
- 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..f8896d69f 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<string, ClientCapabilities> _capabilitiesCache;
+ private readonly object _capabilitiesSyncLock = new object();
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
- private readonly object _capabilitiesSyncLock = new object();
-
public DeviceManager(
IAuthenticationRepository authRepo,
IJsonSerializer json,
@@ -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..7e0cfe424 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);
}
}
@@ -414,7 +422,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;
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index b9992ae73..02ae55071 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;
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
-{
- /// <summary>
- /// Class RefreshUsersMetadata.
- /// </summary>
- public class RefreshUsersMetadata : IScheduledTask, IConfigurableScheduledTask
- {
- /// <summary>
- /// The user manager.
- /// </summary>
- private readonly IUserManager _userManager;
- private readonly IFileSystem _fileSystem;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="RefreshUsersMetadata" /> class.
- /// </summary>
- public RefreshUsersMetadata(IUserManager userManager, IFileSystem fileSystem)
- {
- _userManager = userManager;
- _fileSystem = fileSystem;
- }
-
- /// <inheritdoc />
- public string Name => "Refresh Users";
-
- /// <inheritdoc />
- public string Key => "RefreshUsers";
-
- /// <inheritdoc />
- public string Description => "Refresh user infos";
-
- /// <inheritdoc />
- public string Category => "Library";
-
- /// <inheritdoc />
- public bool IsHidden => true;
-
- /// <inheritdoc />
- public bool IsEnabled => true;
-
- /// <inheritdoc />
- public bool IsLogged => true;
-
- /// <inheritdoc />
- public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
- {
- foreach (var user in _userManager.Users)
- {
- cancellationToken.ThrowIfCancellationRequested();
-
- await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
- }
- }
-
- /// <inheritdoc />
- public IEnumerable<TaskTriggerInfo> 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..083fe4237 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
/// <inheritdoc />
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<User> e)
- {
- var dto = _userManager.GetUserDto(e.Argument);
-
- await SendMessageToUserSession(e.Argument, "UserPolicyUpdated", dto).ConfigureAwait(false);
- }
-
- private async void OnUserConfigurationUpdated(object sender, GenericEventArgs<User> e)
- {
- var dto = _userManager.GetUserDto(e.Argument);
-
- await SendMessageToUserSession(e.Argument, "UserConfigurationUpdated", dto).ConfigureAwait(false);
- }
-
private async Task SendMessageToAdminSessions<T>(string name, T data)
{
try
@@ -210,10 +194,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/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..072e94cdd 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.");
}
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 129faeaab..9558cb4c6 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -149,9 +149,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/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index a57219143..7d74ced6c 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)
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 00826c7cb..02add6600 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<string>() : NormalizeLanguage(user.Configuration.SubtitleLanguagePreference);
+
+ var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
+ ? Array.Empty<string>() : 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<string>()
- : 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)
@@ -534,7 +535,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 +546,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 +557,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;
@@ -670,13 +668,14 @@ 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)
{
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/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index bde77ee8e..f37cb3e9d 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
{
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
-{
- /// <summary>
- /// Class UserManager.
- /// </summary>
- public class UserManager : IUserManager
- {
- private readonly object _policySyncLock = new object();
- private readonly object _configSyncLock = new object();
-
- private readonly ILogger<UserManager> _logger;
- private readonly IUserRepository _userRepository;
- private readonly IXmlSerializer _xmlSerializer;
- private readonly IJsonSerializer _jsonSerializer;
- private readonly INetworkManager _networkManager;
- private readonly IImageProcessor _imageProcessor;
- private readonly Lazy<IDtoService> _dtoServiceFactory;
- private readonly IServerApplicationHost _appHost;
- private readonly IFileSystem _fileSystem;
- private readonly ICryptoProvider _cryptoProvider;
-
- private ConcurrentDictionary<Guid, User> _users;
-
- private IAuthenticationProvider[] _authenticationProviders;
- private DefaultAuthenticationProvider _defaultAuthenticationProvider;
-
- private InvalidAuthProvider _invalidAuthProvider;
-
- private IPasswordResetProvider[] _passwordResetProviders;
- private DefaultPasswordResetProvider _defaultPasswordResetProvider;
-
- private IDtoService DtoService => _dtoServiceFactory.Value;
-
- public UserManager(
- ILogger<UserManager> logger,
- IUserRepository userRepository,
- IXmlSerializer xmlSerializer,
- INetworkManager networkManager,
- IImageProcessor imageProcessor,
- Lazy<IDtoService> 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<GenericEventArgs<User>> UserPasswordChanged;
-
- /// <summary>
- /// Occurs when [user updated].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserUpdated;
-
- public event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
-
- public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
-
- public event EventHandler<GenericEventArgs<User>> UserLockedOut;
-
- public event EventHandler<GenericEventArgs<User>> UserCreated;
-
- /// <summary>
- /// Occurs when [user deleted].
- /// </summary>
- public event EventHandler<GenericEventArgs<User>> UserDeleted;
-
- /// <inheritdoc />
- public IEnumerable<User> Users => _users.Values;
-
- /// <inheritdoc />
- public IEnumerable<Guid> UsersIds => _users.Keys;
-
- /// <summary>
- /// Called when [user updated].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserUpdated(User user)
- {
- UserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
- }
-
- /// <summary>
- /// Called when [user deleted].
- /// </summary>
- /// <param name="user">The user.</param>
- private void OnUserDeleted(User user)
- {
- UserDeleted?.Invoke(this, new GenericEventArgs<User>(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<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
- {
- _authenticationProviders = authenticationProviders.ToArray();
-
- _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
-
- _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
-
- _passwordResetProviders = passwordResetProviders.ToArray();
-
- _defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
- }
-
- /// <inheritdoc />
- 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<User> 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>(user));
- _logger.LogWarning(
- "Disabling user {UserName} due to {Attempts} unsuccessful login attempts.",
- user.Name,
- invalidLogins);
- }
-
- UpdateUserPolicy(user, user.Policy, false);
- }
-
- /// <summary>
- /// Loads the users from the repository.
- /// </summary>
- private void LoadUsers()
- {
- var users = _userRepository.RetrieveAllUsers();
-
- // There always has to be at least one user.
- if (users.Count != 0)
- {
- _users = new ConcurrentDictionary<Guid, User>(
- users.Select(x => new KeyValuePair<Guid, User>(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<Guid, User>();
- _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;
- }
- }
-
- /// <summary>
- /// Refreshes metadata for each user
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public async Task RefreshUsersMetadata(CancellationToken cancellationToken)
- {
- foreach (var user in Users)
- {
- await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem)), cancellationToken).ConfigureAwait(false);
- }
- }
-
- /// <summary>
- /// Renames the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="newName">The new name.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException">user</exception>
- /// <exception cref="ArgumentException"></exception>
- 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);
- }
-
- /// <summary>
- /// Updates the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <exception cref="ArgumentNullException">user</exception>
- /// <exception cref="ArgumentException"></exception>
- 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);
- }
-
- /// <summary>
- /// Creates the user.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>User.</returns>
- /// <exception cref="ArgumentNullException">name</exception>
- /// <exception cref="ArgumentException"></exception>
- 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>(user), _logger);
-
- return user;
- }
-
- /// <inheritdoc />
- /// <exception cref="ArgumentNullException">The <c>user</c> is <c>null</c>.</exception>
- /// <exception cref="ArgumentException">The <c>user</c> doesn't exist, or is the last administrator.</exception>
- /// <exception cref="InvalidOperationException">The <c>user</c> can't be deleted; there are no other users.</exception>
- 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);
- }
-
- /// <summary>
- /// Resets the password by clearing it.
- /// </summary>
- /// <returns>Task.</returns>
- 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>(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>(user));
- }
-
- /// <summary>
- /// Instantiates the new user.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <returns>User.</returns>
- private static User InstantiateNewUser(string name)
- {
- return new User
- {
- Name = name,
- Id = Guid.NewGuid(),
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow
- };
- }
-
- public async Task<ForgotPasswordResult> 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<PinRedeemResult> 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<string>()
- };
- }
-
- 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<UserPolicy>(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>(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<UserConfiguration>(json);
- }
-
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- lock (_configSyncLock)
- {
- _xmlSerializer.SerializeToFile(config, path);
- user.Configuration = config;
- }
-
- if (fireEvent)
- {
- UserConfigurationUpdated?.Invoke(this, new GenericEventArgs<User>(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<User> 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<ICollectionFolder> parents, string viewType, string localizationKey, string sortName, User user, string[] presetViews)
+ private Folder GetUserView(
+ List<ICollectionFolder> 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<string>();
var query = new InternalItemsQuery(user)
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index a88e0f31f..e39092f97 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
{
@@ -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<Tuple<BaseItemDto, string, string>>() {
+ var list = new List<Tuple<BaseItemDto, string, string>>
+ {
new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
};
@@ -2167,20 +2167,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<User> GetEnabledUsers()
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/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
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
- 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/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/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 1bfc9a9a5..ecc3eeb39 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();
}
/// <inheritdoc />
@@ -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<GroupInfoView>();
}
@@ -341,7 +318,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 +326,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 +342,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 +369,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..6999668d1 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();
}
@@ -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<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
IsPlayed = true,
Limit = 1,
@@ -204,7 +209,6 @@ namespace Emby.Server.Implementations.TV
},
EnableImages = false
}
-
}).FirstOrDefault();
Func<Episode> getEpisode = () =>
@@ -219,7 +223,7 @@ namespace Emby.Server.Implementations.TV
IsPlayed = false,
IsVirtualItem = false,
ParentIndexNumberNotEquals = 0,
- MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName,
+ MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions
}).Cast<Episode>().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<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day)
+ {
+ var days = new List<DayOfWeek>(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
+{
+ /// <summary>
+ /// An entity representing a user's access schedule.
+ /// </summary>
+ public class AccessSchedule
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AccessSchedule"/> class.
+ /// </summary>
+ /// <param name="dayOfWeek">The day of the week.</param>
+ /// <param name="startHour">The start hour.</param>
+ /// <param name="endHour">The end hour.</param>
+ /// <param name="userId">The associated user's id.</param>
+ public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
+ {
+ UserId = userId;
+ DayOfWeek = dayOfWeek;
+ StartHour = startHour;
+ EndHour = endHour;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AccessSchedule"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// </summary>
+ protected AccessSchedule()
+ {
+ }
+
+ /// <summary>
+ /// Gets or sets the id of this instance.
+ /// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
+ [XmlIgnore]
+ [Key]
+ [Required]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int Id { get; protected set; }
+
+ /// <summary>
+ /// Gets or sets the id of the associated user.
+ /// </summary>
+ [XmlIgnore]
+ [Required]
+ public Guid UserId { get; protected set; }
+
+ /// <summary>
+ /// Gets or sets the day of week.
+ /// </summary>
+ /// <value>The day of week.</value>
+ [Required]
+ public DynamicDayOfWeek DayOfWeek { get; set; }
+
+ /// <summary>
+ /// Gets or sets the start hour.
+ /// </summary>
+ /// <value>The start hour.</value>
+ [Required]
+ public double StartHour { get; set; }
+
+ /// <summary>
+ /// Gets or sets the end hour.
+ /// </summary>
+ /// <value>The end hour.</value>
+ [Required]
+ public double EndHour { get; set; }
+
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="dayOfWeek">The day of the week.</param>
+ /// <param name="startHour">The start hour.</param>
+ /// <param name="endHour">The end hour.</param>
+ /// <param name="userId">The associated user's id.</param>
+ /// <returns>The newly created instance.</returns>
+ 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/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
+ /// <summary>
+ /// An entity representing a group.
+ /// </summary>
+ public partial class Group : IHasPermissions, ISavingChanges
{
- partial void Init();
-
/// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// Initializes a new instance of the <see cref="Group"/> class.
+ /// Public constructor with required data.
/// </summary>
- protected Group()
+ /// <param name="name">The name of the group.</param>
+ public Group(string name)
{
- GroupPermissions = new HashSet<Permission>();
+ if (string.IsNullOrEmpty(name))
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ Name = name;
+ Id = Guid.NewGuid();
+
+ Permissions = new HashSet<Permission>();
ProviderMappings = new HashSet<ProviderMapping>();
Preferences = new HashSet<Preference>();
@@ -22,66 +35,45 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static Group CreateGroupUnsafe()
- {
- return new Group();
- }
-
- /// <summary>
- /// Public constructor with required data
+ /// Initializes a new instance of the <see cref="Group"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- /// <param name="name"></param>
- /// <param name="_user0"></param>
- public Group(string name, User _user0)
+ protected Group()
{
- 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<Permission>();
- this.ProviderMappings = new HashSet<ProviderMapping>();
- this.Preferences = new HashSet<Preference>();
-
Init();
}
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="name"></param>
- /// <param name="_user0"></param>
- public static Group Create(string name, User _user0)
- {
- return new Group(name, _user0);
- }
-
/*************************************************************************
* Properties
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Gets or sets the id of this group.
/// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
[Key]
[Required]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; protected set; }
+ public Guid Id { get; protected set; }
/// <summary>
- /// Required, Max length = 255
+ /// Gets or sets the group's name.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 255.
+ /// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string Name { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
/// </summary>
+ /// <remarks>
+ /// Required, Concurrency Token.
+ /// </remarks>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
@@ -96,7 +88,7 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
[ForeignKey("Permission_GroupPermissions_Id")]
- public virtual ICollection<Permission> GroupPermissions { get; protected set; }
+ public virtual ICollection<Permission> Permissions { get; protected set; }
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
@@ -104,6 +96,27 @@ namespace Jellyfin.Data.Entities
[ForeignKey("Preference_Preferences_Id")]
public virtual ICollection<Preference> Preferences { get; protected set; }
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="name">The name of this group</param>
+ public static Group Create(string name)
+ {
+ return new Group(name);
+ }
+
+ /// <inheritdoc/>
+ public bool HasPermission(PermissionKind kind)
+ {
+ return Permissions.First(p => p.Kind == kind).Value;
+ }
+
+ /// <inheritdoc/>
+ 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/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,64 +1,35 @@
-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
+ /// <summary>
+ /// An entity representing whether the associated user has a specific permission.
+ /// </summary>
+ public partial class Permission : ISavingChanges
{
- partial void Init();
-
- /// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected Permission()
- {
- Init();
- }
-
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// Initializes a new instance of the <see cref="Permission"/> class.
+ /// Public constructor with required data.
/// </summary>
- public static Permission CreatePermissionUnsafe()
+ /// <param name="kind">The permission kind.</param>
+ /// <param name="value">The value of this permission.</param>
+ public Permission(PermissionKind kind, bool value)
{
- return new Permission();
- }
-
- /// <summary>
- /// Public constructor with required data
- /// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- 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);
-
+ Kind = kind;
+ Value = value;
Init();
}
/// <summary>
- /// Static create function (for use in LINQ queries, etc.)
+ /// Initializes a new instance of the <see cref="Permission"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1)
+ protected Permission()
{
- return new Permission(kind, value, _user0, _group1);
+ Init();
}
/*************************************************************************
@@ -66,79 +37,61 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Gets or sets the id of this permission.
/// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
- /// Backing field for Kind
- /// </summary>
- protected Enums.PermissionKind _Kind;
- /// <summary>
- /// When provided in a partial class, allows value of Kind to be changed before setting.
- /// </summary>
- partial void SetKind(Enums.PermissionKind oldValue, ref Enums.PermissionKind newValue);
- /// <summary>
- /// When provided in a partial class, allows value of Kind to be changed before returning.
- /// </summary>
- partial void GetKind(ref Enums.PermissionKind result);
-
- /// <summary>
- /// Required
+ /// Gets or sets the type of this permission.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[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; }
/// <summary>
- /// Required
+ /// Gets or sets a value indicating whether the associated user has this permission.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
public bool Value { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
/// </summary>
+ /// <remarks>
+ /// Required, ConcurrencyToken.
+ /// </remarks>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
- public void OnSavingChanges()
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="kind">The permission kind.</param>
+ /// <param name="value">The value of this permission.</param>
+ /// <returns>The newly created instance.</returns>
+ public static Permission Create(PermissionKind kind, bool value)
{
- RowVersion++;
+ return new Permission(kind, value);
}
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
-
- public virtual event PropertyChangedEventHandler PropertyChanged;
-
- protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ /// <inheritdoc/>
+ public void OnSavingChanges()
{
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ RowVersion++;
}
+ partial void Init();
}
}
-
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
+ /// <summary>
+ /// An entity representing a preference attached to a user or group.
+ /// </summary>
+ public class Preference : ISavingChanges
{
- partial void Init();
-
- /// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
- /// </summary>
- protected Preference()
- {
- Init();
- }
-
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
+ /// Initializes a new instance of the <see cref="Preference"/> class.
+ /// Public constructor with required data.
/// </summary>
- public static Preference CreatePreferenceUnsafe()
+ /// <param name="kind">The preference kind.</param>
+ /// <param name="value">The value.</param>
+ public Preference(PreferenceKind kind, string value)
{
- return new Preference();
- }
-
- /// <summary>
- /// Public constructor with required data
- /// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- 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();
+ Kind = kind;
+ Value = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary>
- /// Static create function (for use in LINQ queries, etc.)
+ /// Initializes a new instance of the <see cref="Preference"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- /// <param name="kind"></param>
- /// <param name="value"></param>
- /// <param name="_user0"></param>
- /// <param name="_group1"></param>
- public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1)
+ protected Preference()
{
- return new Preference(kind, value, _user0, _group1);
}
/*************************************************************************
@@ -65,43 +35,61 @@ namespace Jellyfin.Data.Entities
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Gets or sets the id of this preference.
/// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
[Key]
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
/// <summary>
- /// Required
+ /// Gets or sets the type of this preference.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
- public Enums.PreferenceKind Kind { get; set; }
+ public PreferenceKind Kind { get; protected set; }
/// <summary>
- /// Required, Max length = 65535
+ /// Gets or sets the value of this preference.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 65535.
+ /// </remarks>
[Required]
[MaxLength(65535)]
[StringLength(65535)]
public string Value { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
/// </summary>
+ /// <remarks>
+ /// Required, ConcurrencyToken.
+ /// </remarks>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="kind">The preference kind.</param>
+ /// <param name="value">The value.</param>
+ /// <returns>The new instance.</returns>
+ public static Preference Create(PreferenceKind kind, string value)
+ {
+ return new Preference(kind, value);
+ }
+
+ /// <inheritdoc/>
public void OnSavingChanges()
{
RowVersion++;
}
-
- /*************************************************************************
- * Navigation properties
- *************************************************************************/
-
}
}
-
diff --git a/Jellyfin.Data/Entities/ProviderMapping.cs b/Jellyfin.Data/Entities/ProviderMapping.cs
index 6197bd97b..e479341ad 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();
}
diff --git a/Jellyfin.Data/Entities/Series.cs b/Jellyfin.Data/Entities/Series.cs
index 097b9958e..4f25c38b7 100644
--- a/Jellyfin.Data/Entities/Series.cs
+++ b/Jellyfin.Data/Entities/Series.cs
@@ -20,14 +20,6 @@ namespace Jellyfin.Data.Entities
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static Series CreateSeriesUnsafe()
- {
- return new Series();
- }
-
- /// <summary>
/// Public constructor with required data
/// </summary>
/// <param name="urlid">This is whats gets displayed in the Urls and API requests. This could also be a string.</param>
@@ -57,27 +49,27 @@ namespace Jellyfin.Data.Entities
/// <summary>
/// Backing field for AirsDayOfWeek
/// </summary>
- protected Enums.Weekday? _AirsDayOfWeek;
+ protected DayOfWeek? _AirsDayOfWeek;
/// <summary>
/// When provided in a partial class, allows value of AirsDayOfWeek to be changed before setting.
/// </summary>
- partial void SetAirsDayOfWeek(Enums.Weekday? oldValue, ref Enums.Weekday? newValue);
+ partial void SetAirsDayOfWeek(DayOfWeek? oldValue, ref DayOfWeek? newValue);
/// <summary>
/// When provided in a partial class, allows value of AirsDayOfWeek to be changed before returning.
/// </summary>
- 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)
{
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
+ /// <summary>
+ /// An entity representing a user.
+ /// </summary>
+ public partial class User : IHasPermissions, ISavingChanges
{
- partial void Init();
+ /// <summary>
+ /// The values being delimited here are Guids, so commas work as they do not appear in Guids.
+ /// </summary>
+ private const char Delimiter = ',';
/// <summary>
- /// Default constructor. Protected due to required properties, but present because EF needs it.
+ /// Initializes a new instance of the <see cref="User"/> class.
+ /// Public constructor with required data.
/// </summary>
- protected User()
+ /// <param name="username">The username for the new user.</param>
+ /// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
+ /// <param name="passwordResetProviderId">The Id of the user's password reset provider.</param>
+ public User(string username, string authenticationProviderId, string passwordResetProviderId)
{
- Groups = new HashSet<Group>();
+ 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<AccessSchedule>();
+ // Groups = new HashSet<Group>();
Permissions = new HashSet<Permission>();
- ProviderMappings = new HashSet<ProviderMapping>();
Preferences = new HashSet<Preference>();
-
+ // ProviderMappings = new HashSet<ProviderMapping>();
+
+ // 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();
}
/// <summary>
- /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
- /// </summary>
- public static User CreateUserUnsafe()
- {
- return new User();
- }
-
- /// <summary>
- /// Public constructor with required data
+ /// Initializes a new instance of the <see cref="User"/> class.
+ /// Default constructor. Protected due to required properties, but present because EF needs it.
/// </summary>
- /// <param name="username"></param>
- /// <param name="mustupdatepassword"></param>
- /// <param name="audiolanguagepreference"></param>
- /// <param name="authenticationproviderid"></param>
- /// <param name="invalidloginattemptcount"></param>
- /// <param name="subtitlemode"></param>
- /// <param name="playdefaultaudiotrack"></param>
- public User(string username, bool mustupdatepassword, string audiolanguagepreference, string authenticationproviderid, int invalidloginattemptcount, string subtitlemode, bool playdefaultaudiotrack)
+ protected User()
{
- 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<Group>();
- this.Permissions = new HashSet<Permission>();
- this.ProviderMappings = new HashSet<ProviderMapping>();
- this.Preferences = new HashSet<Preference>();
-
Init();
}
- /// <summary>
- /// Static create function (for use in LINQ queries, etc.)
- /// </summary>
- /// <param name="username"></param>
- /// <param name="mustupdatepassword"></param>
- /// <param name="audiolanguagepreference"></param>
- /// <param name="authenticationproviderid"></param>
- /// <param name="invalidloginattemptcount"></param>
- /// <param name="subtitlemode"></param>
- /// <param name="playdefaultaudiotrack"></param>
- 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
*************************************************************************/
/// <summary>
- /// Identity, Indexed, Required
+ /// Gets or sets the Id of the user.
/// </summary>
+ /// <remarks>
+ /// Identity, Indexed, Required.
+ /// </remarks>
[Key]
[Required]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int Id { get; protected set; }
+ [JsonIgnore]
+ public Guid Id { get; set; }
/// <summary>
- /// Required, Max length = 255
+ /// Gets or sets the user's name.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 255.
+ /// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string Username { get; set; }
/// <summary>
- /// Max length = 65535
+ /// Gets or sets the user's password, or <c>null</c> if none is set.
/// </summary>
+ /// <remarks>
+ /// Max length = 65535.
+ /// </remarks>
[MaxLength(65535)]
[StringLength(65535)]
public string Password { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets the user's easy password, or <c>null</c> if none is set.
+ /// </summary>
+ /// <remarks>
+ /// Max length = 65535.
+ /// </remarks>
+ [MaxLength(65535)]
+ [StringLength(65535)]
+ public string EasyPassword { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the user must update their password.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
public bool MustUpdatePassword { get; set; }
/// <summary>
- /// Required, Max length = 255
+ /// Gets or sets the audio language preference.
/// </summary>
- [Required]
+ /// <remarks>
+ /// Max length = 255.
+ /// </remarks>
[MaxLength(255)]
[StringLength(255)]
public string AudioLanguagePreference { get; set; }
/// <summary>
- /// Required, Max length = 255
+ /// Gets or sets the authentication provider id.
/// </summary>
+ /// <remarks>
+ /// Required, Max length = 255.
+ /// </remarks>
[Required]
[MaxLength(255)]
[StringLength(255)]
public string AuthenticationProviderId { get; set; }
/// <summary>
- /// Max length = 65535
+ /// Gets or sets the password reset provider id.
/// </summary>
- [MaxLength(65535)]
- [StringLength(65535)]
- public string GroupedFolders { get; set; }
+ /// <remarks>
+ /// Required, Max length = 255.
+ /// </remarks>
+ [Required]
+ [MaxLength(255)]
+ [StringLength(255)]
+ public string PasswordResetProviderId { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets the invalid login attempt count.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
public int InvalidLoginAttemptCount { get; set; }
/// <summary>
- /// Max length = 65535
+ /// Gets or sets the last activity date.
/// </summary>
- [MaxLength(65535)]
- [StringLength(65535)]
- public string LatestItemExcludes { get; set; }
-
- public int? LoginAttemptsBeforeLockout { get; set; }
+ public DateTime? LastActivityDate { get; set; }
/// <summary>
- /// Max length = 65535
+ /// Gets or sets the last login date.
/// </summary>
- [MaxLength(65535)]
- [StringLength(65535)]
- public string MyMediaExcludes { get; set; }
+ public DateTime? LastLoginDate { get; set; }
/// <summary>
- /// Max length = 65535
+ /// Gets or sets the number of login attempts the user can make before they are locked out.
/// </summary>
- [MaxLength(65535)]
- [StringLength(65535)]
- public string OrderedViews { get; set; }
+ public int? LoginAttemptsBeforeLockout { get; set; }
/// <summary>
- /// Required, Max length = 255
+ /// Gets or sets the subtitle mode.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
- [MaxLength(255)]
- [StringLength(255)]
- public string SubtitleMode { get; set; }
+ public SubtitlePlaybackMode SubtitleMode { get; set; }
/// <summary>
- /// Required
+ /// Gets or sets a value indicating whether the default audio track should be played.
/// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
[Required]
public bool PlayDefaultAudioTrack { get; set; }
/// <summary>
- /// Max length = 255
+ /// Gets or sets the subtitle language preference.
/// </summary>
+ /// <remarks>
+ /// Max length = 255.
+ /// </remarks>
[MaxLength(255)]
[StringLength(255)]
- public string SubtitleLanguagePrefernce { get; set; }
+ public string SubtitleLanguagePreference { get; set; }
- public bool? DisplayMissingEpisodes { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether missing episodes should be displayed.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool DisplayMissingEpisodes { get; set; }
- public bool? DisplayCollectionsView { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether to display the collections view.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool DisplayCollectionsView { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the user has a local password.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool EnableLocalPassword { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the server should hide played content in "Latest".
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool HidePlayedInLatest { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to remember audio selections on played content.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool RememberAudioSelections { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to remember subtitle selections on played content.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool RememberSubtitleSelections { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable auto-play for the next episode.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool EnableNextEpisodeAutoPlay { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the user should auto-login.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool EnableAutoLogin { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the user can change their preferences.
+ /// </summary>
+ /// <remarks>
+ /// Required.
+ /// </remarks>
+ [Required]
+ public bool EnableUserPreferenceAccess { get; set; }
- public bool? HidePlayedInLatest { get; set; }
+ /// <summary>
+ /// Gets or sets the maximum parental age rating.
+ /// </summary>
+ public int? MaxParentalAgeRating { get; set; }
- public bool? RememberAudioSelections { get; set; }
+ /// <summary>
+ /// Gets or sets the remote client bitrate limit.
+ /// </summary>
+ public int? RemoteClientBitrateLimit { get; set; }
- public bool? RememberSubtitleSelections { get; set; }
+ /// <summary>
+ /// 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.
+ /// </summary>
+ [Required]
+ public long InternalId { get; set; }
- public bool? EnableNextEpisodeAutoPlay { get; set; }
+ /// <summary>
+ /// Gets or sets the user's profile image. Can be <c>null</c>.
+ /// </summary>
+ // [ForeignKey("UserId")]
+ public virtual ImageInfo ProfileImage { get; set; }
- public bool? EnableUserPreferenceAccess { get; set; }
+ [Required]
+ public SyncPlayAccess SyncPlayAccess { get; set; }
/// <summary>
- /// Required, ConcurrenyToken
+ /// Gets or sets the row version.
/// </summary>
+ /// <remarks>
+ /// Required, Concurrency Token.
+ /// </remarks>
[ConcurrencyCheck]
[Required]
public uint RowVersion { get; set; }
- public void OnSavingChanges()
- {
- RowVersion++;
- }
-
/*************************************************************************
* Navigation properties
*************************************************************************/
- [ForeignKey("Group_Groups_Id")]
+
+ /// <summary>
+ /// Gets or sets the list of access schedules this user has.
+ /// </summary>
+ public virtual ICollection<AccessSchedule> AccessSchedules { get; protected set; }
+
+ /*
+ /// <summary>
+ /// Gets or sets the list of groups this user is a member of.
+ /// </summary>
+ [ForeignKey("Group_Groups_Guid")]
public virtual ICollection<Group> Groups { get; protected set; }
+ */
- [ForeignKey("Permission_Permissions_Id")]
+ /// <summary>
+ /// Gets or sets the list of permissions this user has.
+ /// </summary>
+ [ForeignKey("Permission_Permissions_Guid")]
public virtual ICollection<Permission> Permissions { get; protected set; }
+ /*
+ /// <summary>
+ /// Gets or sets the list of provider mappings this user has.
+ /// </summary>
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
+ */
- [ForeignKey("Preference_Preferences_Id")]
+ /// <summary>
+ /// Gets or sets the list of preferences this user has.
+ /// </summary>
+ [ForeignKey("Preference_Preferences_Guid")]
public virtual ICollection<Preference> Preferences { get; protected set; }
+ /// <summary>
+ /// Static create function (for use in LINQ queries, etc.)
+ /// </summary>
+ /// <param name="username">The username for the created user.</param>
+ /// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
+ /// <param name="passwordResetProviderId">The Id of the user's password reset provider.</param>
+ /// <returns>The created instance.</returns>
+ public static User Create(string username, string authenticationProviderId, string passwordResetProviderId)
+ {
+ return new User(username, authenticationProviderId, passwordResetProviderId);
+ }
+
+ /// <inheritdoc/>
+ public void OnSavingChanges()
+ {
+ RowVersion++;
+ }
+
+ /// <summary>
+ /// Checks whether the user has the specified permission.
+ /// </summary>
+ /// <param name="kind">The permission kind.</param>
+ /// <returns><c>True</c> if the user has the specified permission.</returns>
+ public bool HasPermission(PermissionKind kind)
+ {
+ return Permissions.First(p => p.Kind == kind).Value;
+ }
+
+ /// <summary>
+ /// Sets the given permission kind to the provided value.
+ /// </summary>
+ /// <param name="kind">The permission kind.</param>
+ /// <param name="value">The value to set.</param>
+ public void SetPermission(PermissionKind kind, bool value)
+ {
+ Permissions.First(p => p.Kind == kind).Value = value;
+ }
+
+ /// <summary>
+ /// Gets the user's preferences for the given preference kind.
+ /// </summary>
+ /// <param name="preference">The preference kind.</param>
+ /// <returns>A string array containing the user's preferences.</returns>
+ public string[] GetPreference(PreferenceKind preference)
+ {
+ var val = Preferences.First(p => p.Kind == preference).Value;
+
+ return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter);
+ }
+
+ /// <summary>
+ /// Sets the specified preference to the given value.
+ /// </summary>
+ /// <param name="preference">The preference kind.</param>
+ /// <param name="values">The values.</param>
+ public void SetPreference(PreferenceKind preference, string[] values)
+ {
+ Preferences.First(p => p.Kind == preference).Value
+ = string.Join(Delimiter.ToString(CultureInfo.InvariantCulture), values);
+ }
+
+ /// <summary>
+ /// Checks whether this user is currently allowed to use the server.
+ /// </summary>
+ /// <returns><c>True</c> if the current time is within an access schedule, or there are no access schedules.</returns>
+ public bool IsParentalScheduleAllowed()
+ {
+ return AccessSchedules.Count == 0
+ || AccessSchedules.Any(i => IsParentalScheduleAllowed(i, DateTime.UtcNow));
+ }
+
+ /// <summary>
+ /// Checks whether the provided folder is in this user's grouped folders.
+ /// </summary>
+ /// <param name="id">The Guid of the folder.</param>
+ /// <returns><c>True</c> if the folder is in the user's grouped folders.</returns>
+ 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<PreferenceKind>())
+ {
+ Preferences.Add(new Preference(val, string.Empty));
+ }
+ }
+
+ partial void Init();
}
}
-
diff --git a/MediaBrowser.Model/Configuration/DynamicDayOfWeek.cs b/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
{
+ /// <summary>
+ /// The types of user permissions.
+ /// </summary>
public enum PermissionKind
{
- IsAdministrator,
- IsHidden,
- IsDisabled,
- BlockUnrateditems,
- EnbleSharedDeviceControl,
- EnableRemoteAccess,
- EnableLiveTvManagement,
- EnableLiveTvAccess,
- EnableMediaPlayback,
- EnableAudioPlaybackTranscoding,
- EnableVideoPlaybackTranscoding,
- EnableContentDeletion,
- EnableContentDownloading,
- EnableSyncTranscoding,
- EnableMediaConversion,
- EnableAllDevices,
- EnableAllChannels,
- EnableAllFolders,
- EnablePublicSharing,
- AccessSchedules
+ /// <summary>
+ /// Whether the user is an administrator.
+ /// </summary>
+ IsAdministrator = 0,
+
+ /// <summary>
+ /// Whether the user is hidden.
+ /// </summary>
+ IsHidden = 1,
+
+ /// <summary>
+ /// Whether the user is disabled.
+ /// </summary>
+ IsDisabled = 2,
+
+ /// <summary>
+ /// Whether the user can control shared devices.
+ /// </summary>
+ EnableSharedDeviceControl = 3,
+
+ /// <summary>
+ /// Whether the user can access the server remotely.
+ /// </summary>
+ EnableRemoteAccess = 4,
+
+ /// <summary>
+ /// Whether the user can manage live tv.
+ /// </summary>
+ EnableLiveTvManagement = 5,
+
+ /// <summary>
+ /// Whether the user can access live tv.
+ /// </summary>
+ EnableLiveTvAccess = 6,
+
+ /// <summary>
+ /// Whether the user can play media.
+ /// </summary>
+ EnableMediaPlayback = 7,
+
+ /// <summary>
+ /// Whether the server should transcode audio for the user if requested.
+ /// </summary>
+ EnableAudioPlaybackTranscoding = 8,
+
+ /// <summary>
+ /// Whether the server should transcode video for the user if requested.
+ /// </summary>
+ EnableVideoPlaybackTranscoding = 9,
+
+ /// <summary>
+ /// Whether the user can delete content.
+ /// </summary>
+ EnableContentDeletion = 10,
+
+ /// <summary>
+ /// Whether the user can download content.
+ /// </summary>
+ EnableContentDownloading = 11,
+
+ /// <summary>
+ /// Whether to enable sync transcoding for the user.
+ /// </summary>
+ EnableSyncTranscoding = 12,
+
+ /// <summary>
+ /// Whether the user can do media conversion.
+ /// </summary>
+ EnableMediaConversion = 13,
+
+ /// <summary>
+ /// Whether the user has access to all devices.
+ /// </summary>
+ EnableAllDevices = 14,
+
+ /// <summary>
+ /// Whether the user has access to all channels.
+ /// </summary>
+ EnableAllChannels = 15,
+
+ /// <summary>
+ /// Whether the user has access to all folders.
+ /// </summary>
+ EnableAllFolders = 16,
+
+ /// <summary>
+ /// Whether to enable public sharing for the user.
+ /// </summary>
+ EnablePublicSharing = 17,
+
+ /// <summary>
+ /// Whether the user can remotely control other users.
+ /// </summary>
+ EnableRemoteControlOfOtherUsers = 18,
+
+ /// <summary>
+ /// Whether the user is permitted to do playback remuxing.
+ /// </summary>
+ EnablePlaybackRemuxing = 19,
+
+ /// <summary>
+ /// Whether the server should force transcoding on remote connections for the user.
+ /// </summary>
+ 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
{
+ /// <summary>
+ /// The types of user preferences.
+ /// </summary>
public enum PreferenceKind
{
- MaxParentalRating,
- BlockedTags,
- RemoteClientBitrateLimit,
- EnabledDevices,
- EnabledChannels,
- EnabledFolders,
- EnableContentDeletionFromFolders
+ /// <summary>
+ /// A list of blocked tags.
+ /// </summary>
+ BlockedTags = 0,
+
+ /// <summary>
+ /// A list of blocked channels.
+ /// </summary>
+ BlockedChannels = 1,
+
+ /// <summary>
+ /// A list of blocked media folders.
+ /// </summary>
+ BlockedMediaFolders = 2,
+
+ /// <summary>
+ /// A list of enabled devices.
+ /// </summary>
+ EnabledDevices = 3,
+
+ /// <summary>
+ /// A list of enabled channels
+ /// </summary>
+ EnabledChannels = 4,
+
+ /// <summary>
+ /// A list of enabled folders.
+ /// </summary>
+ EnabledFolders = 5,
+
+ /// <summary>
+ /// A list of folders to allow content deletion from.
+ /// </summary>
+ EnableContentDeletionFromFolders = 6,
+
+ /// <summary>
+ /// A list of latest items to exclude.
+ /// </summary>
+ LatestItemExcludes = 7,
+
+ /// <summary>
+ /// A list of media to exclude.
+ /// </summary>
+ MyMediaExcludes = 8,
+
+ /// <summary>
+ /// A list of grouped folders.
+ /// </summary>
+ GroupedFolders = 9,
+
+ /// <summary>
+ /// A list of unrated items to block.
+ /// </summary>
+ BlockUnratedItems = 10,
+
+ /// <summary>
+ /// A list of ordered views.
+ /// </summary>
+ OrderedViews = 11
}
}
diff --git a/MediaBrowser.Model/Configuration/SubtitlePlaybackMode.cs b/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
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
{
/// <summary>
/// Enum SyncPlayAccess.
@@ -8,16 +8,16 @@ namespace MediaBrowser.Model.Configuration
/// <summary>
/// User can create groups and join them.
/// </summary>
- CreateAndJoinGroups,
+ CreateAndJoinGroups = 0,
/// <summary>
/// User can only join already existing groups.
/// </summary>
- JoinGroups,
+ JoinGroups = 1,
/// <summary>
/// SyncPlay is disabled for the user.
/// </summary>
- None
+ None = 2
}
}
diff --git a/MediaBrowser.Model/Configuration/UnratedItem.cs b/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
+{
+ /// <summary>
+ /// An abstraction representing an entity that has permissions.
+ /// </summary>
+ public interface IHasPermissions
+ {
+ /// <summary>
+ /// Gets a collection containing this entity's permissions.
+ /// </summary>
+ ICollection<Permission> Permissions { get; }
+
+ /// <summary>
+ /// Checks whether this entity has the specified permission kind.
+ /// </summary>
+ /// <param name="kind">The kind of permission.</param>
+ /// <returns><c>true</c> if this entity has the specified permission, <c>false</c> otherwise.</returns>
+ bool HasPermission(PermissionKind kind);
+
+ /// <summary>
+ /// Sets the specified permission to the provided value.
+ /// </summary>
+ /// <param name="kind">The kind of permission.</param>
+ /// <param name="value">The value to set.</param>
+ 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 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.5" />
</ItemGroup>
</Project>
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 @@
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
- <Compile Remove="Migrations\20200430214405_InitialSchema.cs" />
- <Compile Remove="Migrations\20200430214405_InitialSchema.Designer.cs" />
</ItemGroup>
<ItemGroup>
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
/// <inheritdoc/>
public partial class JellyfinDb : DbContext
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JellyfinDb"/> class.
+ /// </summary>
+ /// <param name="options">The database context options.</param>
+ public JellyfinDb(DbContextOptions<JellyfinDb> options) : base(options)
+ {
+ }
+
+ /// <summary>
+ /// Gets or sets the default connection string.
+ /// </summary>
+ public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
+
+ public virtual DbSet<AccessSchedule> AccessSchedules { get; set; }
+
public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
+
+ public virtual DbSet<ImageInfo> ImageInfos { get; set; }
+
+ public virtual DbSet<Permission> Permissions { get; set; }
+
+ public virtual DbSet<Preference> Preferences { get; set; }
+
+ public virtual DbSet<User> Users { get; set; }
/*public virtual DbSet<Artwork> Artwork { get; set; }
public virtual DbSet<Book> Books { get; set; }
public virtual DbSet<BookMetadata> BookMetadata { get; set; }
@@ -42,12 +60,10 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
- public virtual DbSet<Permission> Permissions { get; set; }
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<PersonRole> PersonRoles { get; set; }
public virtual DbSet<Photo> Photo { get; set; }
public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
- public virtual DbSet<Preference> Preferences { get; set; }
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
public virtual DbSet<Rating> Ratings { get; set; }
@@ -62,20 +78,21 @@ namespace Jellyfin.Server.Implementations
public virtual DbSet<Series> Series { get; set; }
public virtual DbSet<SeriesMetadata> SeriesMetadata { get; set; }
public virtual DbSet<Track> Tracks { get; set; }
- public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }
- public virtual DbSet<User> Users { get; set; } */
-
- /// <summary>
- /// Gets or sets the default connection string.
- /// </summary>
- public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
+ public virtual DbSet<TrackMetadata> TrackMetadata { get; set; }*/
- /// <inheritdoc />
- public JellyfinDb(DbContextOptions<JellyfinDb> options) : base(options)
+ /// <inheritdoc/>
+ public override int SaveChanges()
{
- }
+ foreach (var saveEntity in ChangeTracker.Entries()
+ .Where(e => e.State == EntityState.Modified)
+ .Select(entry => entry.Entity)
+ .OfType<ISavingChanges>())
+ {
+ saveEntity.OnSavingChanges();
+ }
- partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
+ return base.SaveChanges();
+ }
/// <inheritdoc />
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);
-
/// <inheritdoc />
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<ISavingChanges>())
- {
- 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<JellyfinDb>().Database.Migrate();
+ serviceProvider.GetRequiredService<JellyfinDb>().Database.Migrate();
}
/// <summary>
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
+
+// <auto-generated />
+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<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property<double>("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property<double>("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AccessSchedules");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("DateCreated")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("ItemId")
+ .HasColumnType("TEXT")
+ .HasMaxLength(256);
+
+ b.Property<int>("LogSeverity")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Name")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(512);
+
+ b.Property<string>("Overview")
+ .HasColumnType("TEXT")
+ .HasMaxLength(512);
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("ShortOverview")
+ .HasColumnType("TEXT")
+ .HasMaxLength(512);
+
+ b.Property<string>("Type")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(256);
+
+ b.Property<Guid>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("ActivityLogs");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(512);
+
+ b.Property<Guid?>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("ImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Permission_Permissions_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("Value")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Permission_Permissions_Guid");
+
+ b.ToTable("Permissions");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Preference_Preferences_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("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<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AudioLanguagePreference")
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<string>("AuthenticationProviderId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<bool>("DisplayCollectionsView")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("DisplayMissingEpisodes")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("EasyPassword")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<bool>("EnableAutoLogin")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableLocalPassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableNextEpisodeAutoPlay")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableUserPreferenceAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("HidePlayedInLatest")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("InternalId")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("InvalidLoginAttemptCount")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime?>("LastActivityDate")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("LastLoginDate")
+ .HasColumnType("TEXT");
+
+ b.Property<int?>("LoginAttemptsBeforeLockout")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("MaxParentalAgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("MustUpdatePassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Password")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<string>("PasswordResetProviderId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<bool>("PlayDefaultAudioTrack")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberAudioSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberSubtitleSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("RemoteClientBitrateLimit")
+ .HasColumnType("INTEGER");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("SubtitleLanguagePreference")
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<int>("SubtitleMode")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("SyncPlayAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("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<Guid>(nullable: false),
+ Username = table.Column<string>(maxLength: 255, nullable: false),
+ Password = table.Column<string>(maxLength: 65535, nullable: true),
+ EasyPassword = table.Column<string>(maxLength: 65535, nullable: true),
+ MustUpdatePassword = table.Column<bool>(nullable: false),
+ AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
+ AuthenticationProviderId = table.Column<string>(maxLength: 255, nullable: false),
+ PasswordResetProviderId = table.Column<string>(maxLength: 255, nullable: false),
+ InvalidLoginAttemptCount = table.Column<int>(nullable: false),
+ LastActivityDate = table.Column<DateTime>(nullable: true),
+ LastLoginDate = table.Column<DateTime>(nullable: true),
+ LoginAttemptsBeforeLockout = table.Column<int>(nullable: true),
+ SubtitleMode = table.Column<int>(nullable: false),
+ PlayDefaultAudioTrack = table.Column<bool>(nullable: false),
+ SubtitleLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
+ DisplayMissingEpisodes = table.Column<bool>(nullable: false),
+ DisplayCollectionsView = table.Column<bool>(nullable: false),
+ EnableLocalPassword = table.Column<bool>(nullable: false),
+ HidePlayedInLatest = table.Column<bool>(nullable: false),
+ RememberAudioSelections = table.Column<bool>(nullable: false),
+ RememberSubtitleSelections = table.Column<bool>(nullable: false),
+ EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: false),
+ EnableAutoLogin = table.Column<bool>(nullable: false),
+ EnableUserPreferenceAccess = table.Column<bool>(nullable: false),
+ MaxParentalAgeRating = table.Column<int>(nullable: true),
+ RemoteClientBitrateLimit = table.Column<int>(nullable: true),
+ InternalId = table.Column<long>(nullable: false),
+ SyncPlayAccess = table.Column<int>(nullable: false),
+ RowVersion = table.Column<uint>(nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AccessSchedules",
+ schema: "jellyfin",
+ columns: table => new
+ {
+ Id = table.Column<int>(nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column<Guid>(nullable: false),
+ DayOfWeek = table.Column<int>(nullable: false),
+ StartHour = table.Column<double>(nullable: false),
+ EndHour = table.Column<double>(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<int>(nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column<Guid>(nullable: true),
+ Path = table.Column<string>(maxLength: 512, nullable: false),
+ LastModified = table.Column<DateTime>(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<int>(nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Kind = table.Column<int>(nullable: false),
+ Value = table.Column<bool>(nullable: false),
+ RowVersion = table.Column<uint>(nullable: false),
+ Permission_Permissions_Guid = table.Column<Guid>(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<int>(nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Kind = table.Column<int>(nullable: false),
+ Value = table.Column<string>(maxLength: 65535, nullable: false),
+ RowVersion = table.Column<uint>(nullable: false),
+ Preference_Preferences_Guid = table.Column<Guid>(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 @@
// <auto-generated />
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<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("DayOfWeek")
+ .HasColumnType("INTEGER");
+
+ b.Property<double>("EndHour")
+ .HasColumnType("REAL");
+
+ b.Property<double>("StartHour")
+ .HasColumnType("REAL");
+
+ b.Property<Guid>("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<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Path")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(512);
+
+ b.Property<Guid?>("UserId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("ImageInfos");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Permission_Permissions_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("Value")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Permission_Permissions_Guid");
+
+ b.ToTable("Permissions");
+ });
+
+ modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
+ {
+ b.Property<int>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property<Guid?>("Preference_Preferences_Guid")
+ .HasColumnType("TEXT");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("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<Guid>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property<string>("AudioLanguagePreference")
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<string>("AuthenticationProviderId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<bool>("DisplayCollectionsView")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("DisplayMissingEpisodes")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("EasyPassword")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<bool>("EnableAutoLogin")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableLocalPassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableNextEpisodeAutoPlay")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("EnableUserPreferenceAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("HidePlayedInLatest")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("InternalId")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("InvalidLoginAttemptCount")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime?>("LastActivityDate")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime?>("LastLoginDate")
+ .HasColumnType("TEXT");
+
+ b.Property<int?>("LoginAttemptsBeforeLockout")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("MaxParentalAgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("MustUpdatePassword")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Password")
+ .HasColumnType("TEXT")
+ .HasMaxLength(65535);
+
+ b.Property<string>("PasswordResetProviderId")
+ .IsRequired()
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<bool>("PlayDefaultAudioTrack")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberAudioSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<bool>("RememberSubtitleSelections")
+ .HasColumnType("INTEGER");
+
+ b.Property<int?>("RemoteClientBitrateLimit")
+ .HasColumnType("INTEGER");
+
+ b.Property<uint>("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("SubtitleLanguagePreference")
+ .HasColumnType("TEXT")
+ .HasMaxLength(255);
+
+ b.Property<int>("SubtitleMode")
+ .HasColumnType("INTEGER");
+
+ b.Property<int>("SyncPlayAccess")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("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
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
{
/// <summary>
/// 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
/// <inheritdoc />
public bool HasPassword(User user)
- => !string.IsNullOrEmpty(user.Password);
+ => !string.IsNullOrEmpty(user?.Password);
/// <inheritdoc />
public Task ChangePassword(User user, string newPassword)
@@ -129,7 +131,7 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- 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
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
{
/// <summary>
/// The default password reset provider.
@@ -51,54 +54,49 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
{
- SerializablePasswordReset spr;
- List<string> usersreset = new List<string>();
- foreach (var resetfile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
+ var usersReset = new List<string>();
+ 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<SerializablePasswordReset>(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()
+ };
}
/// <inheritdoc />
- public async Task<ForgotPasswordResult> StartForgotPasswordProcess(MediaBrowser.Controller.Entities.User user, bool isInNetwork)
+ public async Task<ForgotPasswordResult> 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<User> 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
index dc61aacd7..491aba1d4 100644
--- a/Emby.Server.Implementations/Library/InvalidAuthProvider.cs
+++ b/Jellyfin.Server.Implementations/Users/InvalidAuthProvider.cs
@@ -1,8 +1,10 @@
+#nullable enable
+
using System.Threading.Tasks;
+using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Entities;
-namespace Emby.Server.Implementations.Library
+namespace Jellyfin.Server.Implementations.Users
{
/// <summary>
/// An invalid authentication provider.
@@ -40,12 +42,6 @@ namespace Emby.Server.Implementations.Library
}
/// <inheritdoc />
- public string GetPasswordHash(User user)
- {
- return string.Empty;
- }
-
- /// <inheritdoc />
public string GetEasyPasswordHash(User user)
{
return string.Empty;
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
+{
+ /// <summary>
+ /// Manages the creation and retrieval of <see cref="User"/> instances.
+ /// </summary>
+ 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<UserManager> _logger;
+
+ private IAuthenticationProvider[] _authenticationProviders = null!;
+ private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!;
+ private InvalidAuthProvider _invalidAuthProvider = null!;
+ private IPasswordResetProvider[] _passwordResetProviders = null!;
+ private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserManager"/> class.
+ /// </summary>
+ /// <param name="dbProvider">The database provider.</param>
+ /// <param name="cryptoProvider">The cryptography provider.</param>
+ /// <param name="networkManager">The network manager.</param>
+ /// <param name="appHost">The application host.</param>
+ /// <param name="imageProcessor">The image processor.</param>
+ /// <param name="logger">The logger.</param>
+ public UserManager(
+ JellyfinDbProvider dbProvider,
+ ICryptoProvider cryptoProvider,
+ INetworkManager networkManager,
+ IApplicationHost appHost,
+ IImageProcessor imageProcessor,
+ ILogger<UserManager> logger)
+ {
+ _dbProvider = dbProvider;
+ _cryptoProvider = cryptoProvider;
+ _networkManager = networkManager;
+ _appHost = appHost;
+ _imageProcessor = imageProcessor;
+ _logger = logger;
+ }
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<User>>? OnUserPasswordChanged;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<User>>? OnUserUpdated;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<User>>? OnUserCreated;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<User>>? OnUserDeleted;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut;
+
+ /// <inheritdoc/>
+ public IEnumerable<User> Users => _dbProvider.CreateContext().Users;
+
+ /// <inheritdoc/>
+ public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
+
+ /// <inheritdoc/>
+ 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);
+ }
+
+ /// <inheritdoc/>
+ 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));
+ }
+
+ /// <inheritdoc/>
+ 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>(user));
+ }
+
+ /// <inheritdoc/>
+ public void UpdateUser(User user)
+ {
+ var dbContext = _dbProvider.CreateContext();
+ dbContext.Users.Update(user);
+ dbContext.SaveChanges();
+ }
+
+ /// <inheritdoc/>
+ public async Task UpdateUserAsync(User user)
+ {
+ var dbContext = _dbProvider.CreateContext();
+ dbContext.Users.Update(user);
+
+ await dbContext.SaveChangesAsync().ConfigureAwait(false);
+ }
+
+ /// <inheritdoc/>
+ 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<User>(newUser));
+
+ return newUser;
+ }
+
+ /// <inheritdoc/>
+ 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>(user));
+ }
+
+ /// <inheritdoc/>
+ public Task ResetPassword(User user)
+ {
+ return ChangePassword(user, string.Empty);
+ }
+
+ /// <inheritdoc/>
+ public void ResetEasyPassword(User user)
+ {
+ ChangeEasyPassword(user, string.Empty, null);
+ }
+
+ /// <inheritdoc/>
+ 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>(user));
+ }
+
+ /// <inheritdoc/>
+ public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
+ {
+ GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1);
+ UpdateUser(user);
+
+ OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
+ }
+
+ /// <inheritdoc/>
+ 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<UnratedItem>).ToArray()
+ }
+ };
+ }
+
+ /// <inheritdoc/>
+ public async Task<User?> 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;
+ }
+
+ /// <inheritdoc/>
+ public async Task<ForgotPasswordResult> 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
+ };
+ }
+
+ /// <inheritdoc/>
+ public async Task<PinRedeemResult> 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<string>()
+ };
+ }
+
+ /// <inheritdoc/>
+ public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
+ {
+ _authenticationProviders = authenticationProviders.ToArray();
+ _passwordResetProviders = passwordResetProviders.ToArray();
+
+ _invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
+ _defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
+ _defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
+ }
+
+ /// <inheritdoc />
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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<string>());
+ 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();
+ }
+
+ /// <inheritdoc/>
+ 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<IAuthenticationProvider> 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<IAuthenticationProvider>
+ {
+ _invalidAuthProvider
+ };
+ }
+
+ return providers;
+ }
+
+ private IList<IPasswordResetProvider> 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>(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<JellyfinDb>(
- 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<JellyfinDbProvider>();
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
+ serviceCollection.AddSingleton<IUserManager, UserManager>();
base.RegisterServices(serviceCollection);
}
@@ -79,7 +84,11 @@ namespace Jellyfin.Server
/// <inheritdoc />
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
{
+ // Jellyfin.Server
yield return typeof(CoreAppHost).Assembly;
+
+ // Jellyfin.Server.Implementations
+ yield return typeof(JellyfinDb).Assembly;
}
/// <inheritdoc />
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)
};
/// <summary>
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
+{
+ /// <summary>
+ /// The migration routine for migrating the user database to EF Core.
+ /// </summary>
+ public class MigrateUserDb : IMigrationRoutine
+ {
+ private const string DbFilename = "users.db";
+
+ private readonly ILogger<MigrateUserDb> _logger;
+ private readonly IServerApplicationPaths _paths;
+ private readonly JellyfinDbProvider _provider;
+ private readonly MyXmlSerializer _xmlSerializer;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="paths">The server application paths.</param>
+ /// <param name="provider">The database provider.</param>
+ /// <param name="xmlSerializer">The xml serializer.</param>
+ public MigrateUserDb(
+ ILogger<MigrateUserDb> logger,
+ IServerApplicationPaths paths,
+ JellyfinDbProvider provider,
+ MyXmlSerializer xmlSerializer)
+ {
+ _logger = logger;
+ _paths = paths;
+ _provider = provider;
+ _xmlSerializer = xmlSerializer;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C");
+
+ /// <inheritdoc/>
+ public string Name => "MigrateUserDatabase";
+
+ /// <inheritdoc/>
+ 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<UserMockup>(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..2ece16ee1 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.");
}
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 5eb72cdb1..bd67ec41f 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;
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index fdb15d96a..0b8ddeacd 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<ImageType>(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);
}
/// <summary>
@@ -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);
}
/// <summary>
@@ -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,7 +607,35 @@ namespace MediaBrowser.Api.Images
itemId,
request,
imageInfo,
- cropwhitespace,
+ cropWhitespace,
+ outputFormats,
+ cacheDuration,
+ responseHeaders,
+ isHeadRequest);
+ }
+
+ public Task<object> 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<string, string>
+ {
+ {"transferMode.dlna.org", "Interactive"},
+ {"realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*"}
+ };
+
+ var outputFormats = GetOutputFormats(request);
+
+ return GetImageResult(user.Id,
+ request,
+ imageInfo,
outputFormats,
cacheDuration,
responseHeaders,
@@ -605,6 +643,55 @@ namespace MediaBrowser.Api.Images
}
private async Task<object> GetImageResult(
+ Guid itemId,
+ ImageRequest request,
+ ItemImageInfo info,
+ IReadOnlyCollection<ImageFormat> supportedFormats,
+ TimeSpan? cacheDuration,
+ IDictionary<string, string> 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<object> GetImageResult(
BaseItem item,
Guid itemId,
ImageRequest request,
@@ -741,13 +828,35 @@ namespace MediaBrowser.Api.Images
/// <param name="request">The request.</param>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
- 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;
+ }
+
/// <summary>
/// Posts the image.
/// </summary>
@@ -758,22 +867,41 @@ namespace MediaBrowser.Api.Images
/// <returns>Task.</returns>
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
{
+ var memoryStream = await GetMemoryStream(inputStream);
+
+ // Handle image/png; charset=utf-8
+ mimeType = mimeType.Split(';').FirstOrDefault();
+
+ await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
+
+ entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+ }
+
+ private static async Task<MemoryStream> GetMemoryStream(Stream inputStream)
+ {
using var reader = new StreamReader(inputStream);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
var bytes = Convert.FromBase64String(text);
-
- var memoryStream = new MemoryStream(bytes)
+ 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(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
-
- entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
+ await _providerManager
+ .SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path)
+ .ConfigureAwait(false);
+ await _userManager.UpdateUserAsync(user);
}
}
}
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 9d53a2610..2b4694925 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
{
@@ -763,8 +769,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..279fd6ee9 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;
@@ -859,7 +860,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.");
}
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index a9c7f5d6f..2d61299c7 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
{
@@ -251,7 +253,12 @@ namespace MediaBrowser.Api.Movies
return categories.OrderBy(i => i.RecommendationType);
}
- private IEnumerable<RecommendationDto> GetWithDirector(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+ private IEnumerable<RecommendationDto> GetWithDirector(
+ User user,
+ IEnumerable<string> names,
+ int itemLimit,
+ DtoOptions dtoOptions,
+ RecommendationType type)
{
var itemTypes = new List<string> { typeof(Movie).Name };
if (ServerConfigurationManager.Configuration.EnableExternalContentInSuggestions)
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
index cacec8d64..7d10c9427 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;
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 24297d500..957e4ae91 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);
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index e2d771ec6..2c6534cc0 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)
{
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/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/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index cd8e8dfbe..0c23d8b29 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -378,7 +378,7 @@ namespace MediaBrowser.Api
{
var user = _userManager.GetUserById(request.UserId);
- var series = GetSeries(request.Id, user);
+ var series = GetSeries(request.Id);
if (series == null)
{
@@ -404,7 +404,7 @@ namespace MediaBrowser.Api
};
}
- private Series GetSeries(string seriesId, User user)
+ private Series GetSeries(string seriesId)
{
if (!string.IsNullOrWhiteSpace(seriesId))
{
@@ -433,7 +433,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 +446,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..a1ec08467 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;
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<Guid>();
- 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<BaseItem>
{
Items = Array.Empty<BaseItem>(),
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/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.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/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);
+
/// <summary>
/// Processes the image.
/// </summary>
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..0eabb4b7a 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
@@ -481,12 +482,12 @@ namespace MediaBrowser.Controller.Entities
public virtual bool IsAuthorizedToDelete(User user, List<Folder> 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)
@@ -1005,7 +1006,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>PlayAccess.</returns>
public PlayAccess GetPlayAccess(User user)
{
- if (!user.Policy.EnableMediaPlayback)
+ if (!user.HasPermission(PermissionKind.EnableMediaPlayback))
{
return PlayAccess.None;
}
@@ -1214,11 +1215,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");
}
@@ -1774,7 +1775,7 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- var maxAllowedRating = user.Policy.MaxParentalRating;
+ var maxAllowedRating = user.MaxParentalAgeRating;
if (maxAllowedRating == null)
{
@@ -1790,7 +1791,7 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
- return !GetBlockUnratedValue(user.Policy);
+ return !GetBlockUnratedValue(user);
}
var value = LocalizationManager.GetRatingLevel(rating);
@@ -1798,7 +1799,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 +1861,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 +1887,18 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Gets the block unrated value.
/// </summary>
- /// <param name="config">The configuration.</param>
+ /// <param name="user">The configuration.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
- 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());
}
/// <summary>
@@ -2132,7 +2128,8 @@ namespace MediaBrowser.Controller.Entities
/// <param name="resetPosition">if set to <c>true</c> [reset position].</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException"></exception>
- public virtual void MarkPlayed(User user,
+ public virtual void MarkPlayed(
+ User user,
DateTime? datePlayed,
bool resetPosition)
{
@@ -2806,14 +2803,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/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<DayOfWeek> GetDaysOfWeek(DynamicDayOfWeek day)
- {
- return GetDaysOfWeek(new List<DynamicDayOfWeek> { day });
- }
-
- public static List<DayOfWeek> GetDaysOfWeek(List<DynamicDayOfWeek> days)
- {
- var list = new List<DayOfWeek>();
-
- 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..4af74f9cd 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;
}
@@ -882,7 +890,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<double>(), CancellationToken.None).Result;
@@ -952,11 +960,13 @@ namespace MediaBrowser.Controller.Entities
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);
}
- private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
+ private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
+ IEnumerable<BaseItem> items,
InternalItemsQuery query,
BaseItem queryParent,
User user,
- IServerConfigurationManager configurationManager, ICollectionManager collectionManager)
+ IServerConfigurationManager configurationManager,
+ ICollectionManager collectionManager)
{
if (items == null)
{
@@ -1582,7 +1592,7 @@ namespace MediaBrowser.Controller.Entities
EnableTotalRecordCount = false
};
- if (!user.Configuration.DisplayMissingEpisodes)
+ if (!user.DisplayMissingEpisodes)
{
query.IsVirtualItem = false;
}
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<UnratedItem>(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
/// <value>The display order.</value>
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<Series>();
- return series == null ? Guid.Empty : series.Id;
+ return series?.Id ?? Guid.Empty;
}
/// <summary>
@@ -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<string, SortOrder>(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<string, SortOrder>(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
-{
- /// <summary>
- /// Class User
- /// </summary>
- public class User : BaseItem
- {
- public static IUserManager UserManager { get; set; }
-
- /// <summary>
- /// Gets or sets the password.
- /// </summary>
- /// <value>The password.</value>
- 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;
- }
-
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- [JsonIgnore]
- public override string Path
- {
- get => ConfigurationDirectoryPath;
- set => base.Path = value;
- }
-
- private string _name;
- /// <summary>
- /// Gets or sets the name.
- /// </summary>
- /// <value>The name.</value>
- public override string Name
- {
- get => _name;
- set
- {
- _name = value;
-
- // lazy load this again
- SortName = null;
- }
- }
-
- /// <summary>
- /// Returns the folder containing the item.
- /// If the item is a folder, it returns the folder itself
- /// </summary>
- /// <value>The containing folder path.</value>
- [JsonIgnore]
- public override string ContainingFolderPath => Path;
-
- /// <summary>
- /// Gets the root folder.
- /// </summary>
- /// <value>The root folder.</value>
- [JsonIgnore]
- public Folder RootFolder => LibraryManager.GetUserRootFolder();
-
- /// <summary>
- /// Gets or sets the last login date.
- /// </summary>
- /// <value>The last login date.</value>
- public DateTime? LastLoginDate { get; set; }
- /// <summary>
- /// Gets or sets the last activity date.
- /// </summary>
- /// <value>The last activity date.</value>
- 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;
- }
-
- /// <summary>
- /// Renames the user.
- /// </summary>
- /// <param name="newName">The new name.</param>
- /// <returns>Task.</returns>
- /// <exception cref="ArgumentNullException"></exception>
- 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);
- }
-
- /// <summary>
- /// Gets the path to the user's configuration directory
- /// </summary>
- /// <value>The configuration directory path.</value>
- [JsonIgnore]
- public string ConfigurationDirectoryPath => GetConfigurationDirectoryPath(Name);
-
- public override double GetDefaultPrimaryImageAspectRatio()
- {
- return 1;
- }
-
- /// <summary>
- /// Gets the configuration directory path.
- /// </summary>
- /// <param name="username">The username.</param>
- /// <returns>System.String.</returns>
- 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..bf0e56cf7 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
{
@@ -140,14 +145,15 @@ namespace MediaBrowser.Controller.Entities
return parent.QueryRecursive(query);
}
- var list = new List<BaseItem>();
-
- 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<BaseItem>
+ {
+ 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);
}
@@ -293,21 +299,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<BaseItem>();
-
- 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<BaseItem>
+ {
+ 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);
}
@@ -417,7 +429,8 @@ namespace MediaBrowser.Controller.Entities
};
}
- private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items,
+ private QueryResult<BaseItem> GetResult<T>(
+ IEnumerable<T> items,
BaseItem queryParent,
InternalItemsQuery query)
where T : BaseItem
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
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{System.String}.</returns>
- Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user);
+ Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, Jellyfin.Data.Entities.User user);
/// <summary>
/// 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
/// <param name="parentId">The parent identifier.</param>
/// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param>
- UserView GetNamedView(User user,
+ UserView GetNamedView(
+ User user,
string name,
Guid parentId,
string viewType,
@@ -302,7 +306,8 @@ namespace MediaBrowser.Controller.Library
/// <param name="name">The name.</param>
/// <param name="viewType">Type of the view.</param>
/// <param name="sortName">Name of the sort.</param>
- 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
/// <param name="reason">The reason.</param>
/// <param name="cancellationToken">The cancellation token.</param>
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;
@@ -17,36 +16,46 @@ namespace MediaBrowser.Controller.Library
public interface IUserManager
{
/// <summary>
- /// Gets the users.
+ /// Occurs when a user is updated.
/// </summary>
- /// <value>The users.</value>
- IEnumerable<User> Users { get; }
+ event EventHandler<GenericEventArgs<User>> OnUserUpdated;
/// <summary>
- /// Gets the user ids.
+ /// Occurs when a user is created.
/// </summary>
- /// <value>The users ids.</value>
- IEnumerable<Guid> UsersIds { get; }
+ event EventHandler<GenericEventArgs<User>> OnUserCreated;
/// <summary>
- /// Occurs when [user updated].
+ /// Occurs when a user is deleted.
/// </summary>
- event EventHandler<GenericEventArgs<User>> UserUpdated;
+ event EventHandler<GenericEventArgs<User>> OnUserDeleted;
/// <summary>
- /// Occurs when [user deleted].
+ /// Occurs when a user's password is changed.
/// </summary>
- event EventHandler<GenericEventArgs<User>> UserDeleted;
+ event EventHandler<GenericEventArgs<User>> OnUserPasswordChanged;
- event EventHandler<GenericEventArgs<User>> UserCreated;
-
- event EventHandler<GenericEventArgs<User>> UserPolicyUpdated;
+ /// <summary>
+ /// Occurs when a user is locked out.
+ /// </summary>
+ event EventHandler<GenericEventArgs<User>> OnUserLockedOut;
- event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
+ /// <summary>
+ /// Gets the users.
+ /// </summary>
+ /// <value>The users.</value>
+ IEnumerable<User> Users { get; }
- event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
+ /// <summary>
+ /// Gets the user ids.
+ /// </summary>
+ /// <value>The users ids.</value>
+ IEnumerable<Guid> UsersIds { get; }
- event EventHandler<GenericEventArgs<User>> UserLockedOut;
+ /// <summary>
+ /// Initializes the user manager and ensures that a user exists.
+ /// </summary>
+ void Initialize();
/// <summary>
/// Gets a user by Id.
@@ -64,13 +73,6 @@ namespace MediaBrowser.Controller.Library
User GetUserByName(string name);
/// <summary>
- /// Refreshes metadata for each user
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task RefreshUsersMetadata(CancellationToken cancellationToken);
-
- /// <summary>
/// Renames the user.
/// </summary>
/// <param name="user">The user.</param>
@@ -89,19 +91,27 @@ namespace MediaBrowser.Controller.Library
void UpdateUser(User user);
/// <summary>
- /// Creates the user.
+ /// Updates the user.
/// </summary>
- /// <param name="name">The name.</param>
- /// <returns>User.</returns>
+ /// <param name="user">The user.</param>
+ /// <exception cref="ArgumentNullException">If user is <c>null</c>.</exception>
+ /// <exception cref="ArgumentException">If the provided user doesn't exist.</exception>
+ /// <returns>A task representing the update of the user.</returns>
+ Task UpdateUserAsync(User user);
+
+ /// <summary>
+ /// Creates a user with the specified name.
+ /// </summary>
+ /// <param name="name">The name of the new user.</param>
+ /// <returns>The created user.</returns>
/// <exception cref="ArgumentNullException">name</exception>
/// <exception cref="ArgumentException"></exception>
User CreateUser(string name);
/// <summary>
- /// Deletes the user.
+ /// Deletes the specified user.
/// </summary>
- /// <param name="user">The user.</param>
- /// <returns>Task.</returns>
+ /// <param name="user">The user to be deleted.</param>
void DeleteUser(User user);
/// <summary>
@@ -112,13 +122,6 @@ namespace MediaBrowser.Controller.Library
Task ResetPassword(User user);
/// <summary>
- /// Gets the offline user dto.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>UserDto.</returns>
- UserDto GetOfflineUserDto(User user);
-
- /// <summary>
/// Resets the easy password.
/// </summary>
/// <param name="user">The user.</param>
@@ -163,47 +166,34 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
- /// <summary>
- /// Gets the user policy.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>UserPolicy.</returns>
- UserPolicy GetUserPolicy(User user);
+ void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
- /// <summary>
- /// Gets the user configuration.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>UserConfiguration.</returns>
- UserConfiguration GetUserConfiguration(User user);
+ NameIdPair[] GetAuthenticationProviders();
+
+ NameIdPair[] GetPasswordResetProviders();
/// <summary>
- /// Updates the configuration.
+ /// 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 <see cref="UpdateUser"/>.
/// </summary>
- /// <param name="userId">The user identifier.</param>
- /// <param name="newConfiguration">The new configuration.</param>
- /// <returns>Task.</returns>
- void UpdateConfiguration(Guid userId, UserConfiguration newConfiguration);
-
- void UpdateConfiguration(User user, UserConfiguration newConfiguration);
+ /// <param name="userId">The user's Id.</param>
+ /// <param name="config">The request containing the new user configuration.</param>
+ void UpdateConfiguration(Guid userId, UserConfiguration config);
/// <summary>
- /// Updates the user policy.
+ /// 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 <see cref="UpdateUser"/>.
/// </summary>
- /// <param name="userId">The user identifier.</param>
- /// <param name="userPolicy">The user policy.</param>
- void UpdateUserPolicy(Guid userId, UserPolicy userPolicy);
+ /// <param name="userId">The user's Id.</param>
+ /// <param name="policy">The request containing the new user policy.</param>
+ void UpdatePolicy(Guid userId, UserPolicy policy);
/// <summary>
- /// Makes the valid username.
+ /// Clears the user's profile image.
/// </summary>
- /// <param name="username">The username.</param>
- /// <returns>System.String.</returns>
- string MakeValidUsername(string username);
-
- void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
-
- NameIdPair[] GetAuthenticationProviders();
- NameIdPair[] GetPasswordResetProviders();
+ /// <param name="user">The user.</param>
+ void ClearProfileImage(User user);
}
}
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/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index e89bc05aa..5b901d223 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;
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 2d2f73148..8ce106469 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;
@@ -2148,7 +2149,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 +2165,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";
}
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<string, string> 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
{
/// <summary>
- /// Gets or sets the user identifier.
+ /// Gets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
- public Guid UserId => User == null ? Guid.Empty : User.Id;
+ public Guid UserId => User?.Id ?? Guid.Empty;
/// <summary>
/// Gets or sets the device identifier.
/// </summary>
/// <value>The device identifier.</value>
public string DeviceId { get; set; }
+
/// <summary>
/// Gets or sets the device.
/// </summary>
/// <value>The device.</value>
public string Device { get; set; }
+
/// <summary>
/// Gets or sets the client.
/// </summary>
/// <value>The client.</value>
public string Client { get; set; }
+
/// <summary>
/// Gets or sets the version.
/// </summary>
/// <value>The version.</value>
public string Version { get; set; }
+
/// <summary>
/// Gets or sets the token.
/// </summary>
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/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
-{
- /// <summary>
- /// Provides an interface to implement a User repository
- /// </summary>
- public interface IUserRepository : IRepository
- {
- /// <summary>
- /// Deletes the user.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>Task.</returns>
- void DeleteUser(User user);
-
- /// <summary>
- /// Retrieves all users.
- /// </summary>
- /// <returns>IEnumerable{User}.</returns>
- List<User> 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/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
/// <returns>Task.</returns>
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);
+
/// <summary>
/// Adds the metadata providers.
/// </summary>
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
/// <param name="deviceName">Name of the device.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
- 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.
/// </summary>
/// <value>The user.</value>
- User User { get; set; }
+ Jellyfin.Data.Entities.User User { get; set; }
/// <summary>
/// Gets or sets the user manager.
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/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
/// <summary>
/// The box rear.
/// </summary>
- BoxRear = 11
+ BoxRear = 11,
+
+ /// <summary>
+ /// The user profile image.
+ /// </summary>
+ Profile = 12
}
}
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/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<string>();
- 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<string> 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/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 13a4436e1..eeb74ec96 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<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
{
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
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<User, ItemLookupInfo>
- {
- public UserMetadataService(
- IServerConfigurationManager serverConfigurationManager,
- ILogger<UserMetadataService> logger,
- IProviderManager providerManager,
- IFileSystem fileSystem,
- ILibraryManager libraryManager)
- : base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
- {
- }
-
- /// <inheritdoc />
- protected override void MergeData(MetadataResult<User> source, MetadataResult<User> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
- {
- ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
- }
- }
-}
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>();
- user.Policy.IsAdministrator = isAdmin;
+ user.SetPermission(PermissionKind.IsAdministrator, isAdmin);
_jellyfinAuthServiceMock.Setup(
a => a.Authenticate(