From 0c9a94aab6f62e7de284f9390cd2cb1bf7113525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Wed, 13 Jul 2022 16:18:51 +0200 Subject: [PATCH 01/20] Fixing mixed content libraries #1835 , partially implemented #1828 --- EmbyStat.Clients.Base/Http/BaseHttpClient.cs | 1 - EmbyStat.Clients.Tmdb/TmdbClient.cs | 1 - EmbyStat.Clients.TvMaze/TvMazeShowClient.cs | 3 - EmbyStat.Common/Constants.cs | 6 + .../Converters/SessionConverter.cs | 2 +- .../Enums/{PlayType.cs => MediaType.cs} | 2 +- .../Extensions/LibrarySyncTypeExtensions.cs | 16 + EmbyStat.Common/Models/Cards/Card.cs | 4 +- .../Models/Entities/Events/Play.cs | 2 +- .../Models/Entities/Helpers/ILinkedMedia.cs | 1 + EmbyStat.Common/Models/Entities/Library.cs | 3 +- .../Models/Entities/LibrarySyncType.cs | 13 + .../Models/Entities/MediaServerUserView.cs | 3 +- .../Models/Entities/Movies/Movie.cs | 1 + .../Models/Entities/Users/MediaServerUser.cs | 2 - .../HelperClasses/CardViewModel.cs | 4 +- EmbyStat.Controllers/MapProfiles.cs | 22 +- .../MediaServer/MediaServerController.cs | 13 +- .../MediaServerUserStatisticsViewModel.cs | 3 +- .../MediaServer/UserFullViewModel.cs | 28 +- EmbyStat.Controllers/Movie/MovieController.cs | 22 +- .../Movie/MovieStatisticsViewModel.cs | 2 +- EmbyStat.Controllers/Show/ShowController.cs | 19 +- .../Show/ShowStatisticsViewModel.cs | 2 +- EmbyStat.Core/DataStore/EsDbContext.cs | 12 + ...ner.cs => 20220622095519_Init.Designer.cs} | 8 +- ...5060631_Init.cs => 20220622095519_Init.cs} | 5 +- .../20220711095301_SyncTypeAdded.Designer.cs | 26 +- .../Sqlite/20220711095301_SyncTypeAdded.cs | 36 + ...20712101203_MovedSyncTypeTable.Designer.cs | 1607 +++++++++++++++++ .../20220712101203_MovedSyncTypeTable.cs | 42 + ...SyncDateTimeToLibrarySyncTypes.Designer.cs | 67 +- ...112_MovedSyncDateTimeToLibrarySyncTypes.cs | 106 ++ .../Sqlite/EsDbContextModelSnapshot.cs | 47 +- .../Interfaces/IMediaServerRepository.cs | 10 +- .../Interfaces/IMediaServerService.cs | 6 +- .../MediaServers/MediaServerRepository.cs | 71 +- .../MediaServers/MediaServerService.cs | 52 +- .../MediaServers/MediaServerUserStatistics.cs | 2 +- .../Movies/Interfaces/IMovieRepository.cs | 3 +- .../Movies/Interfaces/IMovieService.cs | 2 +- EmbyStat.Core/Movies/MovieRepository.cs | 18 +- EmbyStat.Core/Movies/MovieService.cs | 32 +- EmbyStat.Core/Movies/MovieStatistics.cs | 2 +- EmbyStat.Core/People/PersonRepository.cs | 8 + .../Sessions/Interfaces/ISessionRepository.cs | 6 +- .../Sessions/Interfaces/ISessionService.cs | 2 +- EmbyStat.Core/Sessions/SessionService.cs | 2 +- .../Shows/Interfaces/IShowRepository.cs | 1 + EmbyStat.Core/Shows/ShowRepository.cs | 7 +- EmbyStat.Core/Shows/ShowService.cs | 42 +- EmbyStat.Core/Shows/ShowStatistics.cs | 2 +- EmbyStat.Core/System/SystemService.cs | 14 +- EmbyStat.DI/DiExtensions.cs | 1 - EmbyStat.Hosts.Cmd/DefaultConfig.cs | 2 +- EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj | 1 - EmbyStat.Hosts.Cmd/Program.cs | 4 +- EmbyStat.Hosts.Cmd/Startup.cs | 1 - EmbyStat.Jobs/BaseJob.cs | 6 +- EmbyStat.Jobs/Jobs/Sync/MovieSyncJob.cs | 67 +- EmbyStat.Jobs/Jobs/Sync/ShowSyncJob.cs | 21 +- .../EmbyStat.Migrations.csproj | 22 - .../Sqlite/20220510134722_Init.cs | 1149 ------------ .../ClientApp/public/locales/base.json | 6 +- EmbyStat.Web/ClientApp/src/i18n.tsx | 8 +- .../server/components/EsServerDetails.tsx | 2 +- .../settings/components/EsLanguageCard.tsx | 4 + .../settings/components/EsMediaServerCard.tsx | 4 + .../settings/components/EsPasswordCard.tsx | 6 +- .../settings/components/EsRollbarCard.tsx | 6 +- .../settings/components/EsTmdbApiCard.tsx | 7 +- .../pages/settings/components/EsUserCard.tsx | 7 +- .../pages/users/components/EsUserTable.tsx | 36 +- .../src/pages/users/hooks/useUserDetails.tsx | 22 + .../src/pages/users/subpages/UserDetails.tsx | 35 + .../users/subpages/components/Header.tsx | 27 + .../pages/users/subpages/components/User.tsx | 55 + .../pages/users/subpages/components/index.tsx | 1 + .../src/pages/users/subpages/index.tsx | 1 + EmbyStat.Web/ClientApp/src/routes.tsx | 2 + .../shared/components/buttons/EsButton.tsx | 7 +- .../shared/components/cards/EsSaveCard.tsx | 15 +- .../shared/components/esAvatar/EsAvatar.tsx | 35 + .../src/shared/components/esAvatar/index.tsx | 1 + .../components/esBoolCheck/EsBoolCheck.tsx | 16 + .../shared/components/esBoolCheck/index.tsx | 1 + .../esDateOrNever/EsDateOrNever.tsx | 22 + .../shared/components/esDateOrNever/index.tsx | 1 + .../esLibrarySelector/EsLibrarySelector.tsx | 4 + .../src/shared/models/library/Library.tsx | 1 + .../models/mediaServer/MediaServerUser.tsx | 31 + .../src/shared/services/serverService.tsx | 8 +- EmbyStat.sln | 20 - .../Services/MediaServerServiceTests.cs | 4 +- 94 files changed, 2599 insertions(+), 1483 deletions(-) rename EmbyStat.Common/Enums/{PlayType.cs => MediaType.cs} (84%) create mode 100644 EmbyStat.Common/Extensions/LibrarySyncTypeExtensions.cs create mode 100644 EmbyStat.Common/Models/Entities/LibrarySyncType.cs rename EmbyStat.Core/DataStore/Migrations/Sqlite/{20220615060631_Init.Designer.cs => 20220622095519_Init.Designer.cs} (99%) rename EmbyStat.Core/DataStore/Migrations/Sqlite/{20220615060631_Init.cs => 20220622095519_Init.cs} (99%) rename EmbyStat.Migrations/Sqlite/20220510134722_Init.Designer.cs => EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.Designer.cs (98%) create mode 100644 EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.cs create mode 100644 EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.Designer.cs create mode 100644 EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.cs rename EmbyStat.Migrations/Sqlite/EsDbContextModelSnapshot.cs => EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.Designer.cs (96%) create mode 100644 EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.cs delete mode 100644 EmbyStat.Migrations/EmbyStat.Migrations.csproj delete mode 100644 EmbyStat.Migrations/Sqlite/20220510134722_Init.cs create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/hooks/useUserDetails.tsx create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/subpages/UserDetails.tsx create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/subpages/components/Header.tsx create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/subpages/components/User.tsx create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/subpages/components/index.tsx create mode 100644 EmbyStat.Web/ClientApp/src/pages/users/subpages/index.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esAvatar/EsAvatar.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esAvatar/index.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/EsBoolCheck.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/index.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/EsDateOrNever.tsx create mode 100644 EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/index.tsx diff --git a/EmbyStat.Clients.Base/Http/BaseHttpClient.cs b/EmbyStat.Clients.Base/Http/BaseHttpClient.cs index ee9c9f4c7..38bd6d4e1 100644 --- a/EmbyStat.Clients.Base/Http/BaseHttpClient.cs +++ b/EmbyStat.Clients.Base/Http/BaseHttpClient.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; diff --git a/EmbyStat.Clients.Tmdb/TmdbClient.cs b/EmbyStat.Clients.Tmdb/TmdbClient.cs index 8204e191a..4ccda2c1d 100644 --- a/EmbyStat.Clients.Tmdb/TmdbClient.cs +++ b/EmbyStat.Clients.Tmdb/TmdbClient.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Threading.Tasks; using AutoMapper; -using EmbyStat.Clients.Base.Metadata; using EmbyStat.Common.Models.Show; using EmbyStat.Configuration; using EmbyStat.Configuration.Interfaces; diff --git a/EmbyStat.Clients.TvMaze/TvMazeShowClient.cs b/EmbyStat.Clients.TvMaze/TvMazeShowClient.cs index 932a17d87..b8f0ff655 100644 --- a/EmbyStat.Clients.TvMaze/TvMazeShowClient.cs +++ b/EmbyStat.Clients.TvMaze/TvMazeShowClient.cs @@ -1,8 +1,5 @@ using AutoMapper; -using EmbyStat.Clients.Base.Metadata; -using EmbyStat.Clients.Tmdb; using EmbyStat.Common.Models.Show; -using EmbyStat.Configuration; using EmbyStat.Configuration.Interfaces; using TvMaze.Api.Client; using TvMaze.Api.Client.Models; diff --git a/EmbyStat.Common/Constants.cs b/EmbyStat.Common/Constants.cs index 6776207e7..41b695b35 100644 --- a/EmbyStat.Common/Constants.cs +++ b/EmbyStat.Common/Constants.cs @@ -123,4 +123,10 @@ public static class JwtClaimIdentifiers //COMMON public static string Unknown => "UNKNOWN"; + + public static class Users + { + public static string TotalWatchedEpisodes => "USERS.WATCHED.EPISODES"; + public static string TotalWatchedMovies => "USERS.WATCHED.MOVIES"; + } } \ No newline at end of file diff --git a/EmbyStat.Common/Converters/SessionConverter.cs b/EmbyStat.Common/Converters/SessionConverter.cs index c4092b028..4eb9f3487 100644 --- a/EmbyStat.Common/Converters/SessionConverter.cs +++ b/EmbyStat.Common/Converters/SessionConverter.cs @@ -27,7 +27,7 @@ public static IEnumerable ConvertToSessions(JArray sessions) if (session["PlayState"]["MediaSourceId"] != null) { - Enum.TryParse(session["NowPlayingItem"]["Type"].Value(), true, out PlayType playType); + Enum.TryParse(session["NowPlayingItem"]["Type"].Value(), true, out MediaType playType); var play = new Play { MediaId = session["NowPlayingItem"]["Id"].Value(), diff --git a/EmbyStat.Common/Enums/PlayType.cs b/EmbyStat.Common/Enums/MediaType.cs similarity index 84% rename from EmbyStat.Common/Enums/PlayType.cs rename to EmbyStat.Common/Enums/MediaType.cs index 6a98add37..6435334ef 100644 --- a/EmbyStat.Common/Enums/PlayType.cs +++ b/EmbyStat.Common/Enums/MediaType.cs @@ -1,6 +1,6 @@ namespace EmbyStat.Common.Enums; -public enum PlayType +public enum MediaType { Unknown = 0, Movie = 1, diff --git a/EmbyStat.Common/Extensions/LibrarySyncTypeExtensions.cs b/EmbyStat.Common/Extensions/LibrarySyncTypeExtensions.cs new file mode 100644 index 000000000..c97b1f96a --- /dev/null +++ b/EmbyStat.Common/Extensions/LibrarySyncTypeExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using EmbyStat.Common.Enums; +using EmbyStat.Common.Models.Entities; + +namespace EmbyStat.Common.Extensions; + +public static class LibrarySyncTypeExtensions +{ + public static DateTime? GetLastSyncedDateForLibrary(this IEnumerable items, string libraryId, LibraryType type) + { + return items.FirstOrDefault(x => x.LibraryId == libraryId && x.SyncType == type)?.LastSynced; + } +} \ No newline at end of file diff --git a/EmbyStat.Common/Models/Cards/Card.cs b/EmbyStat.Common/Models/Cards/Card.cs index 66fa56c99..3e44a1589 100644 --- a/EmbyStat.Common/Models/Cards/Card.cs +++ b/EmbyStat.Common/Models/Cards/Card.cs @@ -2,10 +2,10 @@ namespace EmbyStat.Common.Models.Cards; -public class Card +public class Card { public string Title { get; set; } - public T Value { get; set; } + public string Value { get; set; } public CardType Type { get; set; } public string Icon { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Common/Models/Entities/Events/Play.cs b/EmbyStat.Common/Models/Entities/Events/Play.cs index 050a7d971..3cfee28e1 100644 --- a/EmbyStat.Common/Models/Entities/Events/Play.cs +++ b/EmbyStat.Common/Models/Entities/Events/Play.cs @@ -9,7 +9,7 @@ public class Play public Guid Id { get; set; } public string SessionId { get; set; } public string UserId { get; set; } - public PlayType Type { get; set; } + public MediaType Type { get; set; } public string MediaId { get; set; } public ICollection PlayStates { get; set; } public string SubtitleCodec { get; set; } diff --git a/EmbyStat.Common/Models/Entities/Helpers/ILinkedMedia.cs b/EmbyStat.Common/Models/Entities/Helpers/ILinkedMedia.cs index 2f92f77cf..9f0bf1c6d 100644 --- a/EmbyStat.Common/Models/Entities/Helpers/ILinkedMedia.cs +++ b/EmbyStat.Common/Models/Entities/Helpers/ILinkedMedia.cs @@ -7,4 +7,5 @@ public interface ILinkedMedia ICollection People { get; set; } ICollection Genres { get; set; } Library Library { get; set; } + public string LibraryId { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Common/Models/Entities/Library.cs b/EmbyStat.Common/Models/Entities/Library.cs index 41ff06f12..5fec1252f 100644 --- a/EmbyStat.Common/Models/Entities/Library.cs +++ b/EmbyStat.Common/Models/Entities/Library.cs @@ -11,8 +11,7 @@ public class Library public string Name { get; set; } public string Primary { get; set; } public LibraryType Type { get; set; } - public bool Sync { get; set; } - public DateTime? LastSynced { get; set; } public ICollection Movies { get; set; } public ICollection Shows { get; set; } + public ICollection SyncTypes { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Common/Models/Entities/LibrarySyncType.cs b/EmbyStat.Common/Models/Entities/LibrarySyncType.cs new file mode 100644 index 000000000..d067fe4ce --- /dev/null +++ b/EmbyStat.Common/Models/Entities/LibrarySyncType.cs @@ -0,0 +1,13 @@ + using System; + using EmbyStat.Common.Enums; + + namespace EmbyStat.Common.Models.Entities; + +public class LibrarySyncType +{ + public string Id { get; set; } + public string LibraryId { get; set; } + public Library Library { get; set; } + public LibraryType SyncType { get; set; } + public DateTime? LastSynced { get; set; } +} \ No newline at end of file diff --git a/EmbyStat.Common/Models/Entities/MediaServerUserView.cs b/EmbyStat.Common/Models/Entities/MediaServerUserView.cs index ed27d871c..1e22b43d0 100644 --- a/EmbyStat.Common/Models/Entities/MediaServerUserView.cs +++ b/EmbyStat.Common/Models/Entities/MediaServerUserView.cs @@ -1,4 +1,5 @@ using System; +using EmbyStat.Common.Enums; using EmbyStat.Common.Models.Entities.Users; namespace EmbyStat.Common.Models.Entities; @@ -7,7 +8,7 @@ public class MediaServerUserView { public string UserId { get; set; } public MediaServerUser User { get; set; } - public string MediaType { get; set; } + public MediaType MediaType { get; set; } public string MediaId { get; set; } public int PlayCount { get; set; } public DateTime LastPlayedDate { get; set; } diff --git a/EmbyStat.Common/Models/Entities/Movies/Movie.cs b/EmbyStat.Common/Models/Entities/Movies/Movie.cs index 891ec4f62..cd4244620 100644 --- a/EmbyStat.Common/Models/Entities/Movies/Movie.cs +++ b/EmbyStat.Common/Models/Entities/Movies/Movie.cs @@ -27,5 +27,6 @@ public override int GetHashCode() public ICollection People { get; set; } public ICollection Genres { get; set; } public Library Library { get; set; } + public string LibraryId { get; set; } #endregion } \ No newline at end of file diff --git a/EmbyStat.Common/Models/Entities/Users/MediaServerUser.cs b/EmbyStat.Common/Models/Entities/Users/MediaServerUser.cs index a7bdadb0c..fef43ca07 100644 --- a/EmbyStat.Common/Models/Entities/Users/MediaServerUser.cs +++ b/EmbyStat.Common/Models/Entities/Users/MediaServerUser.cs @@ -14,12 +14,10 @@ public class MediaServerUser public string PrimaryImageTag { get; set; } public DateTimeOffset? LastLoginDate { get; set; } public DateTimeOffset? LastActivityDate { get; set; } - public bool PlayDefaultAudioTrack { get; set; } public string SubtitleLanguagePreference { get; set; } public bool DisplayMissingEpisodes { get; set; } public string SubtitleMode { get; set; } - public bool IsAdministrator { get; set; } public bool IsHidden { get; set; } public bool IsHiddenRemotely { get; set; } diff --git a/EmbyStat.Controllers/HelperClasses/CardViewModel.cs b/EmbyStat.Controllers/HelperClasses/CardViewModel.cs index 4e57331d6..e6933b1a8 100644 --- a/EmbyStat.Controllers/HelperClasses/CardViewModel.cs +++ b/EmbyStat.Controllers/HelperClasses/CardViewModel.cs @@ -1,9 +1,9 @@ namespace EmbyStat.Controllers.HelperClasses; -public class CardViewModel +public class CardViewModel { public string Title { get; set; } - public T Value { get; set; } + public string Value { get; set; } public string Type { get; set; } public string Icon { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Controllers/MapProfiles.cs b/EmbyStat.Controllers/MapProfiles.cs index e1c20697b..dfb3c6470 100644 --- a/EmbyStat.Controllers/MapProfiles.cs +++ b/EmbyStat.Controllers/MapProfiles.cs @@ -67,10 +67,10 @@ public MapProfiles() src => src.MapFrom((org, _) => org.EndTimeUtc?.ToString("O") ?? string.Empty)); - CreateMap(typeof(Card<>), typeof(CardViewModel<>)) + CreateMap() .ForMember("Type", src => src.MapFrom((org, _) => { - return ((Card) org).Type switch + return ((Card) org).Type switch { CardType.Text => "text", CardType.Size => "size", @@ -79,7 +79,8 @@ public MapProfiles() }; })); - CreateMap(); + CreateMap() + .ForMember(x => x.Sync, x => x.Ignore()); CreateMap(); CreateMap() @@ -145,13 +146,16 @@ private void CreateMediaServerUserMappings() .ForMember(x => x.DisplayMissingEpisodes, x => x.MapFrom(y => y.Configuration.DisplayMissingEpisodes)) .ForMember(x => x.SubtitleMode, x => x.MapFrom(y => y.Configuration.SubtitleMode)); + CreateMap(); CreateMap(); CreateMap(); + CreateMap(); + CreateMap(); CreateMap() .ForMember(x => x.MediaId, x => x.MapFrom(y => y.Id)) - .ForMember(x => x.MediaType, x => x.MapFrom(y => y.Type)) + .ForMember(x => x.MediaType, x => x.MapFrom(y => MapMediaType(y.Type))) .ForMember(x => x.PlayCount, x => x.MapFrom(y => y.UserData.PlayCount)) .ForMember(x => x.UserId, x => x.Ignore()) .ForMember(x => x.LastPlayedDate, x => x.MapFrom(y => y.UserData.LastPlayedDate)); @@ -324,6 +328,16 @@ private void CreateLibraryMappings() .ForMember(x => x.Type, x => x.MapFrom(y => y.CollectionType.ToLibraryType())) .ForMember(x => x.Primary, x => x.MapFrom(y => y.ImageTags.ConvertToImageTag())); } + + private static MediaType MapMediaType(string type) + { + return type switch + { + "Movie" => MediaType.Movie, + "Episode" => MediaType.Episode, + _ => MediaType.Unknown + }; + } } public static class DictionaryMappingExtensions diff --git a/EmbyStat.Controllers/MediaServer/MediaServerController.cs b/EmbyStat.Controllers/MediaServer/MediaServerController.cs index 33f431f29..69a3a8a26 100644 --- a/EmbyStat.Controllers/MediaServer/MediaServerController.cs +++ b/EmbyStat.Controllers/MediaServer/MediaServerController.cs @@ -82,7 +82,8 @@ public async Task GetMediaServerStatus() public async Task GetMediaServerLibraries() { var result = await _mediaServerService.GetMediaServerLibraries(); - return Ok(_mapper.Map>(result)); + var map = _mapper.Map>(result); + return Ok(map); } [HttpPost] @@ -144,13 +145,11 @@ public async Task GetUserById(string id) var mappedUser = _mapper.Map(user); - var viewedEpisodeCount = _mediaServerService.GetViewedEpisodeCountByUserId(id); - var viewedMovieCount = _mediaServerService.GetViewedMovieCountByUserId(id); - var lastWatchedMedia = _mediaServerService.GetUserViewPageByUserId(id, 0,10).ToList(); + var viewedEpisodeCount = await _mediaServerService.GetViewedEpisodeCountByUserId(id); + var viewedMovieCount = await _mediaServerService.GetViewedMovieCountByUserId(id); - mappedUser.ViewedEpisodeCount = _mapper.Map>(viewedEpisodeCount); - mappedUser.ViewedMovieCount = _mapper.Map>(viewedMovieCount); - mappedUser.LastWatchedMedia = _mapper.Map>(lastWatchedMedia); + mappedUser.ViewedEpisodeCount = _mapper.Map(viewedEpisodeCount); + mappedUser.ViewedMovieCount = _mapper.Map(viewedMovieCount); return Ok(mappedUser); } diff --git a/EmbyStat.Controllers/MediaServer/MediaServerUserStatisticsViewModel.cs b/EmbyStat.Controllers/MediaServer/MediaServerUserStatisticsViewModel.cs index 99ca6d561..661ed3121 100644 --- a/EmbyStat.Controllers/MediaServer/MediaServerUserStatisticsViewModel.cs +++ b/EmbyStat.Controllers/MediaServer/MediaServerUserStatisticsViewModel.cs @@ -5,6 +5,5 @@ namespace EmbyStat.Controllers.MediaServer; public class MediaServerUserStatisticsViewModel { - public List> Cards { get; set; } - + public List Cards { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Controllers/MediaServer/UserFullViewModel.cs b/EmbyStat.Controllers/MediaServer/UserFullViewModel.cs index a57f2458a..c87dfedce 100644 --- a/EmbyStat.Controllers/MediaServer/UserFullViewModel.cs +++ b/EmbyStat.Controllers/MediaServer/UserFullViewModel.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using EmbyStat.Controllers.HelperClasses; namespace EmbyStat.Controllers.MediaServer; @@ -7,10 +7,28 @@ public class UserFullViewModel { public string Id { get; set; } public string Name { get; set; } + public string PrimaryImageTag { get; set; } + public DateTimeOffset? LastLoginDate { get; set; } + public DateTimeOffset? LastActivityDate { get; set; } + public string SubtitleLanguagePreference { get; set; } public string ServerId { get; set; } public bool IsAdministrator { get; set; } - public bool Deleted { get; set; } - public CardViewModel ViewedMovieCount { get; set; } - public CardViewModel ViewedEpisodeCount { get; set; } - public IList LastWatchedMedia { get; set; } + public bool IsHidden { get; set; } + public bool IsHiddenRemotely { get; set; } + public bool IsHiddenFromUnusedDevices { get; set; } + public bool IsDisabled { get; set; } + public bool EnableLiveTvAccess { get; set; } + public bool EnableContentDeletion { get; set; } + public bool EnableContentDownloading { get; set; } + public bool EnableSubtitleDownloading { get; set; } + public bool EnableSubtitleManagement { get; set; } + public bool EnableSyncTranscoding { get; set; } + public bool EnableMediaConversion { get; set; } + public int InvalidLoginAttemptCount { get; set; } + public bool EnablePublicSharing { get; set; } + public int RemoteClientBitrateLimit { get; set; } + public int SimultaneousStreamLimit { get; set; } + public bool EnableAllDevices { get; set; } + public CardViewModel ViewedMovieCount { get; set; } + public CardViewModel ViewedEpisodeCount { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Controllers/Movie/MovieController.cs b/EmbyStat.Controllers/Movie/MovieController.cs index 760fa3d58..805630db1 100644 --- a/EmbyStat.Controllers/Movie/MovieController.cs +++ b/EmbyStat.Controllers/Movie/MovieController.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AutoMapper; +using EmbyStat.Common.Enums; using EmbyStat.Common.Models.Query; using EmbyStat.Controllers.HelperClasses; using EmbyStat.Core.Movies.Interfaces; using Microsoft.AspNetCore.Mvc; +using MoreLinq; using Newtonsoft.Json; namespace EmbyStat.Controllers.Movie; @@ -53,12 +56,13 @@ public async Task GetMoviePageList(int skip, int take, string sor public async Task GetMovie(string id) { var result = await _movieService.GetMovie(id); - if (result != null) + if (result == null) { - var movie = _mapper.Map(result); - return Ok(movie); + return NotFound(id); } - return NotFound(id); + + var movie = _mapper.Map(result); + return Ok(movie); } [HttpGet] @@ -66,7 +70,15 @@ public async Task GetMovie(string id) public async Task GetLibraries() { var result = await _movieService.GetMovieLibraries(); - return Ok(_mapper.Map>(result)); + var map = _mapper.Map>(result, opts => + opts.AfterMap((src, dest) => + { + dest.ForEach(x => x.Sync = result + .Single(y => y.Id == x.Id).SyncTypes + .Any(y => y.SyncType == LibraryType.Movies)); + })); + + return Ok(map); } [HttpPost] diff --git a/EmbyStat.Controllers/Movie/MovieStatisticsViewModel.cs b/EmbyStat.Controllers/Movie/MovieStatisticsViewModel.cs index 9a31e5d47..40d4e14eb 100644 --- a/EmbyStat.Controllers/Movie/MovieStatisticsViewModel.cs +++ b/EmbyStat.Controllers/Movie/MovieStatisticsViewModel.cs @@ -6,7 +6,7 @@ namespace EmbyStat.Controllers.Movie; public class MovieStatisticsViewModel { - public List> Cards { get; set; } + public List Cards { get; set; } public List TopCards { get; set; } public List Charts { get; set; } public List Shorts { get; set; } diff --git a/EmbyStat.Controllers/Show/ShowController.cs b/EmbyStat.Controllers/Show/ShowController.cs index 4ac1e461e..8d38c756d 100644 --- a/EmbyStat.Controllers/Show/ShowController.cs +++ b/EmbyStat.Controllers/Show/ShowController.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using AutoMapper; +using EmbyStat.Common.Enums; using EmbyStat.Common.Models.Query; using EmbyStat.Controllers.HelperClasses; using EmbyStat.Core.Shows.Interfaces; using Microsoft.AspNetCore.Mvc; +using MoreLinq.Extensions; using Newtonsoft.Json; namespace EmbyStat.Controllers.Show; @@ -28,10 +31,17 @@ public ShowController(IShowService showService, IMapper mapper) public async Task GetLibraries() { var result = await _showService.GetShowLibraries(); - var convert = _mapper.Map>(result); - return Ok(convert); + var map = _mapper.Map>(result, opts => + opts.AfterMap((src, dest) => + { + dest.ForEach(x => x.Sync = result + .Single(y => y.Id == x.Id).SyncTypes + .Any(y => y.SyncType == LibraryType.TvShow)); + })); + + return Ok(map); } - + [HttpPost] [Route("libraries")] public async Task UpdateLibraries([FromBody] string[] libraryIds) @@ -51,7 +61,8 @@ public async Task GetStatistics() [HttpGet] [Route("list")] - public async Task GetShowPageList(int skip, int take, string sortField, string sortOrder, bool requireTotalCount, string filter) + public async Task GetShowPageList(int skip, int take, string sortField, string sortOrder, + bool requireTotalCount, string filter) { var filtersObj = Array.Empty(); if (filter != null) diff --git a/EmbyStat.Controllers/Show/ShowStatisticsViewModel.cs b/EmbyStat.Controllers/Show/ShowStatisticsViewModel.cs index cf337143a..c9bfa60d1 100644 --- a/EmbyStat.Controllers/Show/ShowStatisticsViewModel.cs +++ b/EmbyStat.Controllers/Show/ShowStatisticsViewModel.cs @@ -6,7 +6,7 @@ namespace EmbyStat.Controllers.Show; public class ShowStatisticsViewModel { - public List> Cards { get; set; } + public List Cards { get; set; } public List TopCards { get; set; } public List BarCharts { get; set; } public List PieCharts { get; set; } diff --git a/EmbyStat.Core/DataStore/EsDbContext.cs b/EmbyStat.Core/DataStore/EsDbContext.cs index 58296c157..9d0ae9f14 100644 --- a/EmbyStat.Core/DataStore/EsDbContext.cs +++ b/EmbyStat.Core/DataStore/EsDbContext.cs @@ -24,6 +24,7 @@ public class EsDbContext : IdentityDbContext public DbSet MediaServerUsers { get; set; } public DbSet Libraries { get; set; } + public DbSet LibrarySyncTypes { get; set; } public DbSet Devices { get; set; } public DbSet Filters { get; set; } public DbSet Jobs { get; set; } @@ -44,6 +45,7 @@ public EsDbContext(DbContextOptions options): base(options) protected override void OnModelCreating(ModelBuilder builder) { + BuildLibrary(builder); BuildPeople(builder); BuildMovies(builder); BuildShows(builder); @@ -61,6 +63,16 @@ protected override void OnModelCreating(ModelBuilder builder) #region Builders + private static void BuildLibrary(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasMany(x => x.SyncTypes) + .WithOne(x => x.Library) + .HasForeignKey(x => x.LibraryId) + .IsRequired() + .OnDelete(DeleteBehavior.Cascade); + } + private static void BuildUserViews(ModelBuilder modelBuilder) { modelBuilder.Entity() diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.Designer.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.Designer.cs similarity index 99% rename from EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.Designer.cs rename to EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.Designer.cs index d60594ee0..349e82ac9 100644 --- a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.Designer.cs +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.Designer.cs @@ -11,13 +11,13 @@ namespace EmbyStat.Core.DataStore.Migrations.Sqlite { [DbContext(typeof(EsDbContext))] - [Migration("20220615060631_Init")] + [Migration("20220622095519_Init")] partial class Init { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Device", b => { @@ -525,8 +525,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("LastPlayedDate") .HasColumnType("TEXT"); - b.Property("MediaType") - .HasColumnType("TEXT"); + b.Property("MediaType") + .HasColumnType("INTEGER"); b.Property("MovieId") .HasColumnType("TEXT"); diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.cs similarity index 99% rename from EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.cs rename to EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.cs index 566fbde6c..03858b76c 100644 --- a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220615060631_Init.cs +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220622095519_Init.cs @@ -1,5 +1,4 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -606,7 +605,7 @@ protected override void Up(MigrationBuilder migrationBuilder) { UserId = table.Column(type: "TEXT", nullable: false), MediaId = table.Column(type: "TEXT", nullable: false), - MediaType = table.Column(type: "TEXT", nullable: true), + MediaType = table.Column(type: "INTEGER", nullable: false), PlayCount = table.Column(type: "INTEGER", nullable: false), LastPlayedDate = table.Column(type: "TEXT", nullable: false), EpisodeId = table.Column(type: "TEXT", nullable: true), diff --git a/EmbyStat.Migrations/Sqlite/20220510134722_Init.Designer.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.Designer.cs similarity index 98% rename from EmbyStat.Migrations/Sqlite/20220510134722_Init.Designer.cs rename to EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.Designer.cs index 141e04174..2c5011612 100644 --- a/EmbyStat.Migrations/Sqlite/20220510134722_Init.Designer.cs +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.Designer.cs @@ -8,16 +8,16 @@ #nullable disable -namespace EmbyStat.Migrations.Sqlite +namespace EmbyStat.Core.DataStore.Migrations.Sqlite { [DbContext(typeof(EsDbContext))] - [Migration("20220510134722_Init")] - partial class Init + [Migration("20220711095301_SyncTypeAdded")] + partial class SyncTypeAdded { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Device", b => { @@ -394,7 +394,7 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("Primary") .HasColumnType("TEXT"); - b.Property("Sync") + b.Property("SyncType") .HasColumnType("INTEGER"); b.Property("Type") @@ -525,8 +525,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("LastPlayedDate") .HasColumnType("TEXT"); - b.Property("MediaType") - .HasColumnType("TEXT"); + b.Property("MediaType") + .HasColumnType("INTEGER"); b.Property("MovieId") .HasColumnType("TEXT"); @@ -599,8 +599,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); @@ -731,8 +731,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); @@ -864,8 +864,8 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.cs new file mode 100644 index 000000000..56b82463a --- /dev/null +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220711095301_SyncTypeAdded.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmbyStat.Core.DataStore.Migrations.Sqlite +{ + public partial class SyncTypeAdded : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Sync", + table: "Libraries"); + + migrationBuilder.AddColumn( + name: "SyncType", + table: "Libraries", + type: "INTEGER", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SyncType", + table: "Libraries"); + + migrationBuilder.AddColumn( + name: "Sync", + table: "Libraries", + type: "INTEGER", + nullable: false, + defaultValue: false); + } + } +} diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.Designer.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.Designer.cs new file mode 100644 index 000000000..31fe93e5f --- /dev/null +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.Designer.cs @@ -0,0 +1,1607 @@ +// +using System; +using EmbyStat.Core.DataStore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EmbyStat.Core.DataStore.Migrations.Sqlite +{ + [DbContext(typeof(EsDbContext))] + [Migration("20220712101203_MovedSyncTypeTable")] + partial class MovedSyncTypeTable + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Device", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AppName") + .HasColumnType("TEXT"); + + b.Property("AppVersion") + .HasColumnType("TEXT"); + + b.Property("DateLastActivity") + .HasColumnType("TEXT"); + + b.Property("IconUrl") + .HasColumnType("TEXT"); + + b.Property("LastUserId") + .HasColumnType("TEXT"); + + b.Property("LastUserName") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Devices"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.EmbyStatUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasColumnType("TEXT"); + + b.Property("_refreshToken") + .HasColumnType("TEXT") + .HasColumnName("RefreshTokens"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.FilterValues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Field") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.Property("_Values") + .HasColumnType("TEXT") + .HasColumnName("Values"); + + b.HasKey("Id"); + + b.ToTable("Filters"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Genre", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Name"); + + b.ToTable("Genres"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Helpers.MediaPerson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("PersonId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ShowId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("MovieId"); + + b.HasIndex("PersonId"); + + b.HasIndex("ShowId"); + + b.ToTable("MediaPerson"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CurrentProgressPercentage") + .HasColumnType("REAL"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("EndTimeUtc") + .HasColumnType("TEXT"); + + b.Property("StartTimeUtc") + .HasColumnType("TEXT"); + + b.Property("State") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("Trigger") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Jobs"); + + b.HasData( + new + { + Id = new Guid("78bc2bf0-abd9-48ef-aeff-9c396d644f2a"), + CurrentProgressPercentage = 0.0, + Description = "UPDATE-CHECKERDESCRIPTION", + State = 0, + Title = "UPDATE-CHECKER", + Trigger = "0 */12 * * *" + }, + new + { + Id = new Guid("41e0bf22-1e6b-4f5d-90be-ec966f746a2f"), + CurrentProgressPercentage = 0.0, + Description = "SYSTEM-SYNCDESCRIPTION", + State = 0, + Title = "SYSTEM-SYNC", + Trigger = "0 2 * * *" + }, + new + { + Id = new Guid("be68900b-ee1d-41ef-b12f-60ef3106052e"), + CurrentProgressPercentage = 0.0, + Description = "SHOW-SYNCDESCRIPTION", + State = 0, + Title = "SHOW-SYNC", + Trigger = "0 3 * * *" + }, + new + { + Id = new Guid("ce1fbc9e-21ee-450b-9cdf-58a0e17ea98e"), + CurrentProgressPercentage = 0.0, + Description = "PINGDESCRIPTION", + State = 0, + Title = "PING", + Trigger = "*/5 * * * *" + }, + new + { + Id = new Guid("b109ca73-0563-4062-a3e2-f7e6a00b73e9"), + CurrentProgressPercentage = 0.0, + Description = "DATABASE-CLEANUPDESCRIPTION", + State = 0, + Title = "DATABASE-CLEANUP", + Trigger = "0 4 * * *" + }, + new + { + Id = new Guid("c40555dc-ea57-4c6e-a225-905223d31c3c"), + CurrentProgressPercentage = 0.0, + Description = "MOVIE-SYNCDESCRIPTION", + State = 0, + Title = "MOVIE-SYNC", + Trigger = "0 2 * * *" + }); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Language", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Code") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Languages"); + + b.HasData( + new + { + Id = "e1940bfd-00c4-46f9-b9ac-a87a5d92e8ca", + Code = "da-DK", + Name = "Dansk" + }, + new + { + Id = "c9df5e5c-75f3-46c2-8095-97fde33531d8", + Code = "de-DE", + Name = "Deutsch" + }, + new + { + Id = "d6401f6b-becf-49e2-b82e-1018b3bf607f", + Code = "el-GR", + Name = "Ελληνικά" + }, + new + { + Id = "62fe1b5e-3328-450b-b24b-fa16bea58870", + Code = "en-US", + Name = "English" + }, + new + { + Id = "c0a60a3b-282e-46f5-aa7f-661c88f2edb0", + Code = "es-ES", + Name = "Español" + }, + new + { + Id = "91cca672-af55-4d55-899a-798826a43773", + Code = "fi-FI", + Name = "Suomi" + }, + new + { + Id = "99142c2f-379e-4a25-879b-ecfe25ee9e7c", + Code = "fr-FR", + Name = "Français" + }, + new + { + Id = "a48a2ef9-3b64-4069-8e31-252abb6d07a3", + Code = "hu-HU", + Name = "Magyar" + }, + new + { + Id = "282182b9-9332-4266-a093-5ff5b7f927a9", + Code = "it-IT", + Name = "Italiano" + }, + new + { + Id = "082d8aaf-f86a-4401-bf0f-c315b3c9d904", + Code = "nl-NL", + Name = "Nederlands" + }, + new + { + Id = "d8b0ae7b-9ba7-4a51-9d7c-94402b51265d", + Code = "no-NO", + Name = "Norsk" + }, + new + { + Id = "f3966f43-3ec6-456e-850f-a2ebfc0b539b", + Code = "pl-PL", + Name = "Polski" + }, + new + { + Id = "b21074db-74b9-4e24-8867-34e82c265256", + Code = "pt-BR", + Name = "Brasileiro" + }, + new + { + Id = "490c1cb5-b711-4514-aa97-d22ddff2b2fa", + Code = "pt-PT", + Name = "Português" + }, + new + { + Id = "6b103b14-20d1-49c0-b7ce-8d701399b64d", + Code = "ro-RO", + Name = "Românesc" + }, + new + { + Id = "3e8d27e9-e314-4d57-967f-cf5d84144acf", + Code = "sv-SE", + Name = "Svenska" + }, + new + { + Id = "97616a9b-60f9-407a-9a87-b4518da5e5f4", + Code = "zh-CN", + Name = "简体中文" + }); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Library", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("LastSynced") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.Property("SyncType") + .HasColumnType("INTEGER"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SyncType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibrarySyncType"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerInfo", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CachePath") + .HasColumnType("TEXT"); + + b.Property("CanLaunchWebBrowser") + .HasColumnType("INTEGER"); + + b.Property("CanSelfRestart") + .HasColumnType("INTEGER"); + + b.Property("CanSelfUpdate") + .HasColumnType("INTEGER"); + + b.Property("HardwareAccelerationRequiresPremiere") + .HasColumnType("INTEGER"); + + b.Property("HasPendingRestart") + .HasColumnType("INTEGER"); + + b.Property("HasUpdateAvailable") + .HasColumnType("INTEGER"); + + b.Property("HttpServerPortNumber") + .HasColumnType("INTEGER"); + + b.Property("HttpsPortNumber") + .HasColumnType("INTEGER"); + + b.Property("InternalMetadataPath") + .HasColumnType("TEXT"); + + b.Property("ItemsByNamePath") + .HasColumnType("TEXT"); + + b.Property("LocalAddress") + .HasColumnType("TEXT"); + + b.Property("LogPath") + .HasColumnType("TEXT"); + + b.Property("OperatingSystem") + .HasColumnType("TEXT"); + + b.Property("OperatingSystemDisplayName") + .HasColumnType("TEXT"); + + b.Property("ProgramDataPath") + .HasColumnType("TEXT"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.Property("SupportsAutoRunAtStartup") + .HasColumnType("INTEGER"); + + b.Property("SupportsHttps") + .HasColumnType("INTEGER"); + + b.Property("SupportsLibraryMonitor") + .HasColumnType("INTEGER"); + + b.Property("SystemUpdateLevel") + .HasColumnType("TEXT"); + + b.Property("TranscodingTempPath") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.Property("WanAddress") + .HasColumnType("TEXT"); + + b.Property("WebSocketPortNumber") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaServerInfo"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("MissedPings") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("MediaServerStatus"); + + b.HasData( + new + { + Id = new Guid("e55668a1-6a81-47ba-882d-738347e7e9cd"), + MissedPings = 0 + }); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerUserView", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("MediaId") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("LastPlayedDate") + .HasColumnType("TEXT"); + + b.Property("MediaType") + .HasColumnType("INTEGER"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("PlayCount") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "MediaId"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.ToTable("MediaServerUserViews"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Movies.Movie", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Banner") + .HasColumnType("TEXT"); + + b.Property("CommunityRating") + .HasColumnType("TEXT"); + + b.Property("Container") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("IMDB") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("TEXT"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OfficialRating") + .HasColumnType("TEXT"); + + b.Property("OriginalTitle") + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PremiereDate") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.Property("ProductionYear") + .HasColumnType("INTEGER"); + + b.Property("RunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("TMDB") + .HasColumnType("INTEGER"); + + b.Property("TVDB") + .HasColumnType("INTEGER"); + + b.Property("Thumb") + .HasColumnType("TEXT"); + + b.Property("Video3DFormat") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CommunityRating"); + + b.HasIndex("LibraryId"); + + b.HasIndex("Logo"); + + b.HasIndex("Name"); + + b.HasIndex("Primary"); + + b.HasIndex("RunTimeTicks"); + + b.HasIndex("SortName"); + + b.ToTable("Movies"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Person", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("People"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.PluginInfo", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Plugins"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Episode", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Banner") + .HasColumnType("TEXT"); + + b.Property("CommunityRating") + .HasColumnType("TEXT"); + + b.Property("Container") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("DvdEpisodeNumber") + .HasColumnType("REAL"); + + b.Property("DvdSeasonNumber") + .HasColumnType("INTEGER"); + + b.Property("IMDB") + .HasColumnType("TEXT"); + + b.Property("IndexNumber") + .HasColumnType("INTEGER"); + + b.Property("IndexNumberEnd") + .HasColumnType("INTEGER"); + + b.Property("LocationType") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OfficialRating") + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PremiereDate") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.Property("ProductionYear") + .HasColumnType("INTEGER"); + + b.Property("RunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("SeasonId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("TMDB") + .HasColumnType("INTEGER"); + + b.Property("TVDB") + .HasColumnType("INTEGER"); + + b.Property("Thumb") + .HasColumnType("TEXT"); + + b.Property("Video3DFormat") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("Episodes"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Season", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Banner") + .HasColumnType("TEXT"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("IndexNumber") + .HasColumnType("INTEGER"); + + b.Property("IndexNumberEnd") + .HasColumnType("INTEGER"); + + b.Property("LocationType") + .HasColumnType("INTEGER"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PremiereDate") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.Property("ProductionYear") + .HasColumnType("INTEGER"); + + b.Property("ShowId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("Thumb") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ShowId"); + + b.ToTable("Season"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Show", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Banner") + .HasColumnType("TEXT"); + + b.Property("CommunityRating") + .HasColumnType("TEXT"); + + b.Property("CumulativeRunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("DateCreated") + .HasColumnType("TEXT"); + + b.Property("ExternalSynced") + .HasColumnType("INTEGER"); + + b.Property("IMDB") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("TEXT"); + + b.Property("Logo") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OfficialRating") + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("PremiereDate") + .HasColumnType("TEXT"); + + b.Property("Primary") + .HasColumnType("TEXT"); + + b.Property("ProductionYear") + .HasColumnType("INTEGER"); + + b.Property("RunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("SizeInMb") + .HasColumnType("REAL"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("TEXT"); + + b.Property("TMDB") + .HasColumnType("INTEGER"); + + b.Property("TVDB") + .HasColumnType("INTEGER"); + + b.Property("Thumb") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CommunityRating"); + + b.HasIndex("LibraryId"); + + b.HasIndex("Logo"); + + b.HasIndex("Name"); + + b.HasIndex("Primary"); + + b.HasIndex("RunTimeTicks"); + + b.HasIndex("SortName"); + + b.ToTable("Shows"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Statistic", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CalculationDateTime") + .HasColumnType("TEXT"); + + b.Property("IsValid") + .HasColumnType("INTEGER"); + + b.Property("JsonResult") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.AudioStream", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BitRate") + .HasColumnType("INTEGER"); + + b.Property("ChannelLayout") + .HasColumnType("TEXT"); + + b.Property("Channels") + .HasColumnType("INTEGER"); + + b.Property("Codec") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("SampleRate") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.ToTable("AudioStream"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.MediaSource", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("BitRate") + .HasColumnType("INTEGER"); + + b.Property("Container") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.Property("Protocol") + .HasColumnType("TEXT"); + + b.Property("RunTimeTicks") + .HasColumnType("INTEGER"); + + b.Property("SizeInMb") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("MovieId"); + + b.HasIndex("SizeInMb"); + + b.ToTable("MediaSource"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.SubtitleStream", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("Codec") + .HasColumnType("TEXT"); + + b.Property("DisplayTitle") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("Language"); + + b.HasIndex("MovieId"); + + b.ToTable("SubtitleStream"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.VideoStream", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AspectRatio") + .HasColumnType("TEXT"); + + b.Property("AverageFrameRate") + .HasColumnType("REAL"); + + b.Property("BitDepth") + .HasColumnType("INTEGER"); + + b.Property("BitRate") + .HasColumnType("INTEGER"); + + b.Property("Channels") + .HasColumnType("INTEGER"); + + b.Property("Codec") + .HasColumnType("TEXT"); + + b.Property("EpisodeId") + .HasColumnType("TEXT"); + + b.Property("Height") + .HasColumnType("INTEGER"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("MovieId") + .HasColumnType("TEXT"); + + b.Property("VideoRange") + .HasColumnType("TEXT"); + + b.Property("Width") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AverageFrameRate"); + + b.HasIndex("BitDepth"); + + b.HasIndex("Codec"); + + b.HasIndex("EpisodeId"); + + b.HasIndex("Height"); + + b.HasIndex("MovieId"); + + b.HasIndex("VideoRange"); + + b.HasIndex("Width"); + + b.ToTable("VideoStream"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Users.MediaServerUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("DisplayMissingEpisodes") + .HasColumnType("INTEGER"); + + b.Property("EnableAllDevices") + .HasColumnType("INTEGER"); + + b.Property("EnableContentDeletion") + .HasColumnType("INTEGER"); + + b.Property("EnableContentDownloading") + .HasColumnType("INTEGER"); + + b.Property("EnableLiveTvAccess") + .HasColumnType("INTEGER"); + + b.Property("EnableMediaConversion") + .HasColumnType("INTEGER"); + + b.Property("EnablePublicSharing") + .HasColumnType("INTEGER"); + + b.Property("EnableSubtitleDownloading") + .HasColumnType("INTEGER"); + + b.Property("EnableSubtitleManagement") + .HasColumnType("INTEGER"); + + b.Property("EnableSyncTranscoding") + .HasColumnType("INTEGER"); + + b.Property("HasConfiguredEasyPassword") + .HasColumnType("INTEGER"); + + b.Property("HasConfiguredPassword") + .HasColumnType("INTEGER"); + + b.Property("HasPassword") + .HasColumnType("INTEGER"); + + b.Property("InvalidLoginAttemptCount") + .HasColumnType("INTEGER"); + + b.Property("IsAdministrator") + .HasColumnType("INTEGER"); + + b.Property("IsDisabled") + .HasColumnType("INTEGER"); + + b.Property("IsHidden") + .HasColumnType("INTEGER"); + + b.Property("IsHiddenFromUnusedDevices") + .HasColumnType("INTEGER"); + + b.Property("IsHiddenRemotely") + .HasColumnType("INTEGER"); + + b.Property("LastActivityDate") + .HasColumnType("TEXT"); + + b.Property("LastLoginDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PlayDefaultAudioTrack") + .HasColumnType("INTEGER"); + + b.Property("PrimaryImageTag") + .HasColumnType("TEXT"); + + b.Property("RemoteClientBitrateLimit") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("TEXT"); + + b.Property("SimultaneousStreamLimit") + .HasColumnType("INTEGER"); + + b.Property("SubtitleLanguagePreference") + .HasColumnType("TEXT"); + + b.Property("SubtitleMode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MediaServerUsers"); + }); + + modelBuilder.Entity("GenreMovie", b => + { + b.Property("GenresId") + .HasColumnType("TEXT"); + + b.Property("MoviesId") + .HasColumnType("TEXT"); + + b.HasKey("GenresId", "MoviesId"); + + b.HasIndex("MoviesId"); + + b.ToTable("GenreMovie"); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.Property("GenresId") + .HasColumnType("TEXT"); + + b.Property("ShowsId") + .HasColumnType("TEXT"); + + b.HasKey("GenresId", "ShowsId"); + + b.HasIndex("ShowsId"); + + b.ToTable("GenreShow"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Roles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("RoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("UserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.ToTable("UserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("UserTokens"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Helpers.MediaPerson", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", "Movie") + .WithMany("People") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EmbyStat.Common.Models.Entities.Person", "Person") + .WithMany("MediaPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Show", "Show") + .WithMany("People") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Movie"); + + b.Navigation("Person"); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Library", "Library") + .WithMany("SyncTypes") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerUserView", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", null) + .WithMany("Views") + .HasForeignKey("EpisodeId"); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", null) + .WithMany("Views") + .HasForeignKey("MovieId"); + + b.HasOne("EmbyStat.Common.Models.Entities.Users.MediaServerUser", "User") + .WithMany("Views") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Movies.Movie", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Library", "Library") + .WithMany("Movies") + .HasForeignKey("LibraryId"); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Episode", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Season", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Season"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Season", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Show", "Show") + .WithMany("Seasons") + .HasForeignKey("ShowId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Show"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Show", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Library", "Library") + .WithMany("Shows") + .HasForeignKey("LibraryId"); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.AudioStream", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", "Episode") + .WithMany("AudioStreams") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", "Movie") + .WithMany("AudioStreams") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.MediaSource", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", "Episode") + .WithMany("MediaSources") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", "Movie") + .WithMany("MediaSources") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.SubtitleStream", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", "Episode") + .WithMany("SubtitleStreams") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", "Movie") + .WithMany("SubtitleStreams") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Streams.VideoStream", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", "Episode") + .WithMany("VideoStreams") + .HasForeignKey("EpisodeId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", "Movie") + .WithMany("VideoStreams") + .HasForeignKey("MovieId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Episode"); + + b.Navigation("Movie"); + }); + + modelBuilder.Entity("GenreMovie", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EmbyStat.Common.Models.Entities.Movies.Movie", null) + .WithMany() + .HasForeignKey("MoviesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreShow", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EmbyStat.Common.Models.Entities.Shows.Show", null) + .WithMany() + .HasForeignKey("ShowsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Library", b => + { + b.Navigation("Movies"); + + b.Navigation("Shows"); + + b.Navigation("SyncTypes"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Movies.Movie", b => + { + b.Navigation("AudioStreams"); + + b.Navigation("MediaSources"); + + b.Navigation("People"); + + b.Navigation("SubtitleStreams"); + + b.Navigation("VideoStreams"); + + b.Navigation("Views"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Person", b => + { + b.Navigation("MediaPeople"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Episode", b => + { + b.Navigation("AudioStreams"); + + b.Navigation("MediaSources"); + + b.Navigation("SubtitleStreams"); + + b.Navigation("VideoStreams"); + + b.Navigation("Views"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Season", b => + { + b.Navigation("Episodes"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Shows.Show", b => + { + b.Navigation("People"); + + b.Navigation("Seasons"); + }); + + modelBuilder.Entity("EmbyStat.Common.Models.Entities.Users.MediaServerUser", b => + { + b.Navigation("Views"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.cs new file mode 100644 index 000000000..d1fb9a983 --- /dev/null +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712101203_MovedSyncTypeTable.cs @@ -0,0 +1,42 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmbyStat.Core.DataStore.Migrations.Sqlite +{ + public partial class MovedSyncTypeTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "LibrarySyncType", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + LibraryId = table.Column(type: "TEXT", nullable: false), + SyncType = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LibrarySyncType", x => x.Id); + table.ForeignKey( + name: "FK_LibrarySyncType_Libraries_LibraryId", + column: x => x.LibraryId, + principalTable: "Libraries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_LibrarySyncType_LibraryId", + table: "LibrarySyncType", + column: "LibraryId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "LibrarySyncType"); + } + } +} diff --git a/EmbyStat.Migrations/Sqlite/EsDbContextModelSnapshot.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.Designer.cs similarity index 96% rename from EmbyStat.Migrations/Sqlite/EsDbContextModelSnapshot.cs rename to EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.Designer.cs index 87bfa6cef..5a36de5de 100644 --- a/EmbyStat.Migrations/Sqlite/EsDbContextModelSnapshot.cs +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.Designer.cs @@ -3,19 +3,21 @@ using EmbyStat.Core.DataStore; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; #nullable disable -namespace EmbyStat.Migrations.Sqlite +namespace EmbyStat.Core.DataStore.Migrations.Sqlite { [DbContext(typeof(EsDbContext))] - partial class EsDbContextModelSnapshot : ModelSnapshot + [Migration("20220712221112_MovedSyncDateTimeToLibrarySyncTypes")] + partial class MovedSyncDateTimeToLibrarySyncTypes { - protected override void BuildModel(ModelBuilder modelBuilder) + protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Device", b => { @@ -383,18 +385,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("TEXT"); - b.Property("LastSynced") - .HasColumnType("TEXT"); - b.Property("Name") .HasColumnType("TEXT"); b.Property("Primary") .HasColumnType("TEXT"); - b.Property("Sync") - .HasColumnType("INTEGER"); - b.Property("Type") .HasColumnType("INTEGER"); @@ -403,6 +399,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Libraries"); }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("LastSynced") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SyncType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibrarySyncTypes"); + }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerInfo", b => { b.Property("Id") @@ -523,8 +541,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastPlayedDate") .HasColumnType("TEXT"); - b.Property("MediaType") - .HasColumnType("TEXT"); + b.Property("MediaType") + .HasColumnType("INTEGER"); b.Property("MovieId") .HasColumnType("TEXT"); @@ -597,8 +615,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); @@ -729,8 +747,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); @@ -862,8 +880,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("TMDB") .HasColumnType("INTEGER"); - b.Property("TVDB") - .HasColumnType("TEXT"); + b.Property("TVDB") + .HasColumnType("INTEGER"); b.Property("Thumb") .HasColumnType("TEXT"); @@ -1354,6 +1372,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Show"); }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Library", "Library") + .WithMany("SyncTypes") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerUserView", b => { b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", null) @@ -1516,6 +1545,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Movies"); b.Navigation("Shows"); + + b.Navigation("SyncTypes"); }); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Movies.Movie", b => diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.cs new file mode 100644 index 000000000..e686b908d --- /dev/null +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/20220712221112_MovedSyncDateTimeToLibrarySyncTypes.cs @@ -0,0 +1,106 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EmbyStat.Core.DataStore.Migrations.Sqlite +{ + public partial class MovedSyncDateTimeToLibrarySyncTypes : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_LibrarySyncType_Libraries_LibraryId", + table: "LibrarySyncType"); + + migrationBuilder.DropPrimaryKey( + name: "PK_LibrarySyncType", + table: "LibrarySyncType"); + + migrationBuilder.DropColumn( + name: "LastSynced", + table: "Libraries"); + + migrationBuilder.DropColumn( + name: "SyncType", + table: "Libraries"); + + migrationBuilder.RenameTable( + name: "LibrarySyncType", + newName: "LibrarySyncTypes"); + + migrationBuilder.RenameIndex( + name: "IX_LibrarySyncType_LibraryId", + table: "LibrarySyncTypes", + newName: "IX_LibrarySyncTypes_LibraryId"); + + migrationBuilder.AddColumn( + name: "LastSynced", + table: "LibrarySyncTypes", + type: "TEXT", + nullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_LibrarySyncTypes", + table: "LibrarySyncTypes", + column: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_LibrarySyncTypes_Libraries_LibraryId", + table: "LibrarySyncTypes", + column: "LibraryId", + principalTable: "Libraries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_LibrarySyncTypes_Libraries_LibraryId", + table: "LibrarySyncTypes"); + + migrationBuilder.DropPrimaryKey( + name: "PK_LibrarySyncTypes", + table: "LibrarySyncTypes"); + + migrationBuilder.DropColumn( + name: "LastSynced", + table: "LibrarySyncTypes"); + + migrationBuilder.RenameTable( + name: "LibrarySyncTypes", + newName: "LibrarySyncType"); + + migrationBuilder.RenameIndex( + name: "IX_LibrarySyncTypes_LibraryId", + table: "LibrarySyncType", + newName: "IX_LibrarySyncType_LibraryId"); + + migrationBuilder.AddColumn( + name: "LastSynced", + table: "Libraries", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "SyncType", + table: "Libraries", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddPrimaryKey( + name: "PK_LibrarySyncType", + table: "LibrarySyncType", + column: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_LibrarySyncType_Libraries_LibraryId", + table: "LibrarySyncType", + column: "LibraryId", + principalTable: "Libraries", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/EmbyStat.Core/DataStore/Migrations/Sqlite/EsDbContextModelSnapshot.cs b/EmbyStat.Core/DataStore/Migrations/Sqlite/EsDbContextModelSnapshot.cs index f44418d32..e933034f5 100644 --- a/EmbyStat.Core/DataStore/Migrations/Sqlite/EsDbContextModelSnapshot.cs +++ b/EmbyStat.Core/DataStore/Migrations/Sqlite/EsDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class EsDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "6.0.5"); + modelBuilder.HasAnnotation("ProductVersion", "6.0.6"); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Device", b => { @@ -383,18 +383,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .HasColumnType("TEXT"); - b.Property("LastSynced") - .HasColumnType("TEXT"); - b.Property("Name") .HasColumnType("TEXT"); b.Property("Primary") .HasColumnType("TEXT"); - b.Property("Sync") - .HasColumnType("INTEGER"); - b.Property("Type") .HasColumnType("INTEGER"); @@ -403,6 +397,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Libraries"); }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("LastSynced") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SyncType") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibrarySyncTypes"); + }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerInfo", b => { b.Property("Id") @@ -523,8 +539,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastPlayedDate") .HasColumnType("TEXT"); - b.Property("MediaType") - .HasColumnType("TEXT"); + b.Property("MediaType") + .HasColumnType("INTEGER"); b.Property("MovieId") .HasColumnType("TEXT"); @@ -1354,6 +1370,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Show"); }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.LibrarySyncType", b => + { + b.HasOne("EmbyStat.Common.Models.Entities.Library", "Library") + .WithMany("SyncTypes") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + modelBuilder.Entity("EmbyStat.Common.Models.Entities.MediaServerUserView", b => { b.HasOne("EmbyStat.Common.Models.Entities.Shows.Episode", null) @@ -1516,6 +1543,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Movies"); b.Navigation("Shows"); + + b.Navigation("SyncTypes"); }); modelBuilder.Entity("EmbyStat.Common.Models.Entities.Movies.Movie", b => diff --git a/EmbyStat.Core/MediaServers/Interfaces/IMediaServerRepository.cs b/EmbyStat.Core/MediaServers/Interfaces/IMediaServerRepository.cs index b59893fb1..c9f538dc0 100644 --- a/EmbyStat.Core/MediaServers/Interfaces/IMediaServerRepository.cs +++ b/EmbyStat.Core/MediaServers/Interfaces/IMediaServerRepository.cs @@ -32,13 +32,15 @@ public interface IMediaServerRepository Task> GetUserPage(int skip, int take, string sortField, string sortOrder); Task GetAllUsers(); Task GetAllAdministrators(); - Task GetUserById(string id); + Task GetUserById(string id); Task DeleteAllUsers(); Task InsertOrUpdateUserViews(List views); Task GetUserCount(); - int GetUserViewsForType(string type); + int GetUserViewsForType(MediaType type); + Task GetMediaServerViewsForUser(string id, MediaType type); Task> GetMostWatchedShows(int count); Task> GetMostWatchedMovies(int count); + #endregion #region Devices @@ -50,10 +52,10 @@ public interface IMediaServerRepository #region Libraries Task> GetAllLibraries(); Task> GetAllLibraries(LibraryType type); - Task> GetAllLibraries(LibraryType type, bool synced); + Task> GetAllSyncedLibraries(LibraryType type); Task SetLibraryAsSynced(string[] libraryIds, LibraryType type); Task DeleteAndInsertLibraries(Library[] libraries); Task DeleteAllLibraries(); - Task UpdateLibrarySyncDate(string libraryId, DateTime date); + Task UpdateLibrarySyncDate(string libraryId, DateTime date, LibraryType type); #endregion } \ No newline at end of file diff --git a/EmbyStat.Core/MediaServers/Interfaces/IMediaServerService.cs b/EmbyStat.Core/MediaServers/Interfaces/IMediaServerService.cs index 3481a2ec1..8542cbf91 100644 --- a/EmbyStat.Core/MediaServers/Interfaces/IMediaServerService.cs +++ b/EmbyStat.Core/MediaServers/Interfaces/IMediaServerService.cs @@ -36,9 +36,9 @@ public interface IMediaServerService Task> GetUserPage(int skip, int take, string sortField, string sortOrder, bool requireTotalCount); Task GetAllUsers(); Task GetAllAdministrators(); - Task GetUserById(string id); - Card GetViewedEpisodeCountByUserId(string id); - Card GetViewedMovieCountByUserId(string id); + Task GetUserById(string id); + Task GetViewedEpisodeCountByUserId(string id); + Task GetViewedMovieCountByUserId(string id); IEnumerable GetUserViewPageByUserId(string id, int page, int size); Task ProcessViewsForUser(string id); Task CalculateMediaServerUserStatistics(); diff --git a/EmbyStat.Core/MediaServers/MediaServerRepository.cs b/EmbyStat.Core/MediaServers/MediaServerRepository.cs index f27df4c37..854449e4f 100644 --- a/EmbyStat.Core/MediaServers/MediaServerRepository.cs +++ b/EmbyStat.Core/MediaServers/MediaServerRepository.cs @@ -84,7 +84,9 @@ public Task DeleteAllPlugins() public Task GetServerInfo() { - return _context.MediaServerInfo.AsNoTracking().SingleOrDefaultAsync(); + return _context.MediaServerInfo + .AsNoTracking() + .SingleOrDefaultAsync(); } public async Task DeleteAndInsertServerInfo(MediaServerInfo entity) @@ -108,10 +110,10 @@ public async Task DeleteAndInsertUsers(IEnumerable users) { var query = MediaServerExtensions.UserInsertQuery; _logger.LogDebug(query); - + await using var connection = _sqliteBootstrap.CreateConnection(); await connection.OpenAsync(); - + await using var transaction = connection.BeginTransaction(); await connection.ExecuteAsync(query, users, transaction); await transaction.CommitAsync(); @@ -119,16 +121,17 @@ public async Task DeleteAndInsertUsers(IEnumerable users) var usersToDelete = await _context.MediaServerUsers .Where(x => !users.Select(u => u.Id).Contains(x.Id)) .ToListAsync(); - + _context.MediaServerUsers.RemoveRange(usersToDelete); await _context.SaveChangesAsync(); } - public async Task> GetUserPage(int skip, int take, string sortField, string sortOrder) + public async Task> GetUserPage(int skip, int take, string sortField, + string sortOrder) { var query = MediaServerExtensions.GenerateUserPageQuery(skip, take, sortField, sortOrder); _logger.LogDebug(query); - + await using var connection = _sqliteBootstrap.CreateConnection(); await connection.OpenAsync(); return await connection.QueryAsync(query); @@ -150,7 +153,7 @@ public Task GetAllAdministrators() .ToArrayAsync(); } - public Task GetUserById(string id) + public Task GetUserById(string id) { return _context.MediaServerUsers .AsNoTracking() @@ -174,7 +177,7 @@ ON CONFLICT (UserId, MediaId) DO _logger.LogDebug(query); await using var connection = _sqliteBootstrap.CreateConnection(); await connection.OpenAsync(); - + await using var transaction = connection.BeginTransaction(); await connection.ExecuteAsync(query, views, transaction); await transaction.CommitAsync(); @@ -185,7 +188,7 @@ public Task GetUserCount() return _context.MediaServerUsers.CountAsync(); } - public int GetUserViewsForType(string type) + public int GetUserViewsForType(MediaType type) { var list = _context.MediaServerUserViews .AsNoTracking() @@ -207,18 +210,18 @@ public async Task> GetMostWatchedShows(int count) GROUP BY s.Id ORDER BY ViewCount DESC LIMIT {count}"; - + _logger.LogDebug(query); await using var connection = _sqliteBootstrap.CreateConnection(); await connection.OpenAsync(); var result = await connection .QueryAsync>(query, - (s, c) => new KeyValuePair(s, Convert.ToInt32(c)), - splitOn: "ViewCount"); + (s, c) => new KeyValuePair(s, Convert.ToInt32(c)), + splitOn: "ViewCount"); return result.ToDictionary(x => x.Key, x => x.Value); } - + public async Task> GetMostWatchedMovies(int count) { var query = $@" @@ -229,7 +232,7 @@ public async Task> GetMostWatchedMovies(int count) GROUP BY m.Id ORDER BY ViewCount DESC LIMIT {count}"; - + _logger.LogDebug(query); await using var connection = _sqliteBootstrap.CreateConnection(); await connection.OpenAsync(); @@ -241,6 +244,14 @@ ORDER BY ViewCount DESC return result.ToDictionary(x => x.Key, x => x.Value); } + public Task GetMediaServerViewsForUser(string id, MediaType type) + { + return _context.MediaServerUserViews + .AsNoTracking() + .Where(x => x.UserId == id && x.MediaType == type) + .CountAsync(); + } + #endregion #region Devices @@ -274,27 +285,41 @@ public Task> GetAllLibraries() public Task> GetAllLibraries(LibraryType type) { + var types = new[] {type, LibraryType.Other}; return _context.Libraries + .Include(x => x.SyncTypes) .AsNoTracking() - .Where(x => x.Type == type).ToListAsync(); + .Where(x => types.Contains(x.Type)) + .ToListAsync(); } - public Task> GetAllLibraries(LibraryType type, bool synced) + public Task> GetAllSyncedLibraries(LibraryType type) { return _context.Libraries - .Where(x => x.Type == type && x.Sync == synced) + .Include(x => x.SyncTypes) + .AsNoTracking() + .Where(x => x.SyncTypes.Any(y => y.SyncType == type)) .ToListAsync(); } public async Task SetLibraryAsSynced(string[] libraryIds, LibraryType type) { var libraries = await _context.Libraries - .Where(x => x.Type == type) + .Include(x => x.SyncTypes) .ToListAsync(); - + foreach (var library in libraries) { - library.Sync = libraryIds.Contains(library.Id); + if (library.SyncTypes.Any(x => x.SyncType == type && !libraryIds.Contains(library.Id))) + { + + library.SyncTypes = library.SyncTypes.Where(x => x.SyncType != type).ToList(); + } + + if (libraryIds.Contains(library.Id) && !library.SyncTypes.Any(x => x.LibraryId == library.Id && x.SyncType == type)) + { + library.SyncTypes.Add(new LibrarySyncType {SyncType = type, Id = Guid.NewGuid().ToString()}); + } } await _context.SaveChangesAsync(); @@ -313,10 +338,10 @@ public async Task DeleteAllLibraries() await _context.SaveChangesAsync(); } - public async Task UpdateLibrarySyncDate(string libraryId, DateTime date) + public async Task UpdateLibrarySyncDate(string libraryId, DateTime date, LibraryType type) { - var library = await _context.Libraries - .Where(x => x.Id == libraryId) + var library = await _context.LibrarySyncTypes + .Where(x => x.LibraryId == libraryId &&x.SyncType == type) .FirstOrDefaultAsync(); if (library != null) diff --git a/EmbyStat.Core/MediaServers/MediaServerService.cs b/EmbyStat.Core/MediaServers/MediaServerService.cs index 9fa50323c..7766958c7 100644 --- a/EmbyStat.Core/MediaServers/MediaServerService.cs +++ b/EmbyStat.Core/MediaServers/MediaServerService.cs @@ -236,9 +236,9 @@ private async Task CalculateShowStatistics() #region Cards - private async Task>> CalculateCards() + private async Task> CalculateCards() { - var list = new List>(); + var list = new List(); list.AddIfNotNull(await CalculateActiveUsers()); list.AddIfNotNull(await CalculateIdleUsers()); list.AddIfNotNull(CalculateWatchedEpisodeCount()); @@ -247,13 +247,13 @@ private async Task>> CalculateCards() } - private Task> CalculateActiveUsers() + private Task CalculateActiveUsers() { return CalculateStat(async () => { var users = await _mediaServerRepository.GetAllUsers(); var count = users.Count(x => x.LastActivityDate > DateTime.Now.AddMonths(-6)); - return new Card + return new Card { Title = Constants.MediaServer.TotalActiveUsers, Value = count.ToString(), @@ -263,13 +263,13 @@ private Task> CalculateActiveUsers() }, "Calculate total active user count failed:"); } - private Task> CalculateIdleUsers() + private Task CalculateIdleUsers() { return CalculateStat(async () => { var users = await _mediaServerRepository.GetAllUsers(); var count = users.Count(x => x.LastActivityDate <= DateTime.Now.AddMonths(-6)); - return new Card + return new Card { Title = Constants.MediaServer.TotalIdleUsers, Value = count.ToString(), @@ -279,12 +279,12 @@ private Task> CalculateIdleUsers() }, "Calculate total idle user count failed:"); } - private Card CalculateWatchedMovieCount() + private Card CalculateWatchedMovieCount() { return CalculateStat(() => { - var viewCount = _mediaServerRepository.GetUserViewsForType("Movie"); - return new Card + var viewCount = _mediaServerRepository.GetUserViewsForType(MediaType.Movie); + return new Card { Title = Constants.Movies.TotalWatchedMovies, Value = viewCount.ToString(), @@ -294,12 +294,12 @@ private Card CalculateWatchedMovieCount() }, "Calculate total idle user count failed:"); } - private Card CalculateWatchedEpisodeCount() + private Card CalculateWatchedEpisodeCount() { return CalculateStat(() => { - var viewCount = _mediaServerRepository.GetUserViewsForType("Episode"); - return new Card + var viewCount = _mediaServerRepository.GetUserViewsForType(MediaType.Episode); + return new Card { Title = Constants.Shows.TotalWatchedEpisodes, Value = viewCount.ToString(), @@ -373,28 +373,28 @@ public async Task GetAllAdministrators() return await _mediaServerRepository.GetAllAdministrators(); } - public Task GetUserById(string id) + public Task GetUserById(string id) { return _mediaServerRepository.GetUserById(id); } - public Card GetViewedEpisodeCountByUserId(string id) + public async Task GetViewedEpisodeCountByUserId(string id) { - var episodeIds = _sessionService.GetMediaIdsForUser(id, PlayType.Episode); - return new Card + var count = await _mediaServerRepository.GetMediaServerViewsForUser(id, MediaType.Episode); + return new Card { - Title = "Constants.Users.TotalWatchedEpisodes", - Value = episodeIds.Count() + Title = Constants.Users.TotalWatchedEpisodes, + Value = count.ToString() }; } - public Card GetViewedMovieCountByUserId(string id) + public async Task GetViewedMovieCountByUserId(string id) { - var movieIds = _sessionService.GetMediaIdsForUser(id, PlayType.Movie); - return new Card + var count = await _mediaServerRepository.GetMediaServerViewsForUser(id, MediaType.Movie); + return new Card { - Title = "Constants.Users.TotalWatchedMovies", - Value = movieIds.Count() + Title = Constants.Users.TotalWatchedMovies, + Value = count.ToString() }; } @@ -496,9 +496,9 @@ public async Task GetAndProcessLibraries() foreach (var library in libraries) { - library.Sync = currentLibraries - .FirstOrDefault(x => x.Id == library.Id)? - .Sync ?? false; + library.SyncTypes = currentLibraries + .FirstOrDefault(x => x.Id == library.Id) + ?.SyncTypes ?? null; } await _mediaServerRepository.DeleteAndInsertLibraries(libraries); diff --git a/EmbyStat.Core/MediaServers/MediaServerUserStatistics.cs b/EmbyStat.Core/MediaServers/MediaServerUserStatistics.cs index a0bd12bd4..254934c3e 100644 --- a/EmbyStat.Core/MediaServers/MediaServerUserStatistics.cs +++ b/EmbyStat.Core/MediaServers/MediaServerUserStatistics.cs @@ -4,5 +4,5 @@ namespace EmbyStat.Core.MediaServers; public class MediaServerUserStatistics { - public List> Cards { get; set; } + public List Cards { get; set; } } \ No newline at end of file diff --git a/EmbyStat.Core/Movies/Interfaces/IMovieRepository.cs b/EmbyStat.Core/Movies/Interfaces/IMovieRepository.cs index 4bda42b74..df5355805 100644 --- a/EmbyStat.Core/Movies/Interfaces/IMovieRepository.cs +++ b/EmbyStat.Core/Movies/Interfaces/IMovieRepository.cs @@ -7,7 +7,7 @@ namespace EmbyStat.Core.Movies.Interfaces; public interface IMovieRepository : IMediaRepository { - Task GetById(string id); + Task GetById(string id); Task UpsertRange(IEnumerable movies); IEnumerable GetAll(); IEnumerable GetAllWithImdbId(); @@ -25,4 +25,5 @@ public interface IMovieRepository : IMediaRepository IEnumerable CalculateCodecFilterValues(); IEnumerable CalculateVideoRangeFilterValues(); Task DeleteAll(); + Task RemoveUnwantedMovies(IEnumerable libraryIds); } \ No newline at end of file diff --git a/EmbyStat.Core/Movies/Interfaces/IMovieService.cs b/EmbyStat.Core/Movies/Interfaces/IMovieService.cs index 624c118f8..a8f74fc55 100644 --- a/EmbyStat.Core/Movies/Interfaces/IMovieService.cs +++ b/EmbyStat.Core/Movies/Interfaces/IMovieService.cs @@ -12,6 +12,6 @@ public interface IMovieService Task CalculateMovieStatistics(); bool TypeIsPresent(); Task> GetMoviePage(int skip, int take, string sortField, string sortOrder, Filter[] filters, bool requireTotalCount); - Task GetMovie(string id); + Task GetMovie(string id); Task SetLibraryAsSynced(string[] libraryIds); } \ No newline at end of file diff --git a/EmbyStat.Core/Movies/MovieRepository.cs b/EmbyStat.Core/Movies/MovieRepository.cs index 3f367aff5..023597272 100644 --- a/EmbyStat.Core/Movies/MovieRepository.cs +++ b/EmbyStat.Core/Movies/MovieRepository.cs @@ -1,3 +1,4 @@ +using System.Linq; using Dapper; using EmbyStat.Common; using EmbyStat.Common.Enums; @@ -39,9 +40,11 @@ public async Task UpsertRange(IEnumerable movies) await connection.ExecuteAsync($"DELETE FROM {Constants.Tables.AudioStreams} WHERE MovieId = @Id", movie, transaction); await connection.ExecuteAsync($"DELETE FROM {Constants.Tables.SubtitleStreams} WHERE MovieId = @Id", movie, transaction); - var movieQuery = @$"INSERT OR REPLACE INTO {Constants.Tables.Movies} (Id,DateCreated,Banner,Logo,""Primary"",Thumb,Name,Path,PremiereDate,ProductionYear,SortName,OriginalTitle,Container,CommunityRating,IMDB,TMDB,TVDB,RunTimeTicks,OfficialRating,Video3DFormat) -VALUES (@Id,@DateCreated,@Banner,@Logo,@Primary,@Thumb,@Name,@Path,@PremiereDate,@ProductionYear,@SortName,@OriginalTitle,@Container,@CommunityRating,@IMDB,@TMDB,@TVDB,@RunTimeTicks,@OfficialRating,@Video3DFormat)"; - await connection.ExecuteAsync(movieQuery, movie, transaction); + var movieQuery = @$"INSERT OR REPLACE INTO {Constants.Tables.Movies} (Id,DateCreated,Banner,Logo,""Primary"",Thumb,Name,Path,PremiereDate,ProductionYear,SortName,OriginalTitle,Container,CommunityRating,IMDB,TMDB,TVDB,RunTimeTicks,OfficialRating,Video3DFormat,LibraryId) +VALUES (@Id,@DateCreated,@Banner,@Logo,@Primary,@Thumb,@Name,@Path,@PremiereDate,@ProductionYear,@SortName,@OriginalTitle,@Container,@CommunityRating,@IMDB,@TMDB,@TVDB,@RunTimeTicks,@OfficialRating,@Video3DFormat,@LibraryId)"; + var movieParams = new DynamicParameters(movie); + movieParams.Add("@LibraryId",movie.Library.Id); + await connection.ExecuteAsync(movieQuery, movieParams, transaction); if (movie.MediaSources.AnyNotNull()) { @@ -106,6 +109,12 @@ public async Task DeleteAll() await _context.SaveChangesAsync(); } + public async Task RemoveUnwantedMovies(IEnumerable libraryIds) + { + _context.Movies.RemoveRange(_context.Movies.Where(x => !libraryIds.Any(y => y == x.LibraryId))); + await _context.SaveChangesAsync(); + } + public IEnumerable GetAll() { return GetAll(false); @@ -114,7 +123,6 @@ public IEnumerable GetAll() public IEnumerable GetAll(bool includeGenres) { var query = _context.Movies.AsQueryable(); - if (includeGenres) { query = query.Include(x => x.Genres); @@ -130,7 +138,7 @@ public IEnumerable GetAllWithImdbId() .OrderBy(x => x.SortName); } - public Task GetById(string id) + public Task GetById(string id) { return _context.Movies .Include(x => x.Genres) diff --git a/EmbyStat.Core/Movies/MovieService.cs b/EmbyStat.Core/Movies/MovieService.cs index 0e05a7a62..cb775a4cc 100644 --- a/EmbyStat.Core/Movies/MovieService.cs +++ b/EmbyStat.Core/Movies/MovieService.cs @@ -105,21 +105,23 @@ public async Task> GetMoviePage(int skip, int take, string sortField return page; } - public Task GetMovie(string id) + public Task GetMovie(string id) { return _movieRepository.GetById(id); } - public Task SetLibraryAsSynced(string[] libraryIds) + public async Task SetLibraryAsSynced(string[] libraryIds) { - return _mediaServerRepository.SetLibraryAsSynced(libraryIds, LibraryType.Movies); + await _mediaServerRepository.SetLibraryAsSynced(libraryIds, LibraryType.Movies); + await _movieRepository.RemoveUnwantedMovies(libraryIds); + await _statisticsRepository.MarkTypesAsInvalid(StatisticType.Movie); } #region Cards - private async Task>> CalculateCards() + private async Task> CalculateCards() { - var list = new List>(); + var list = new List(); list.AddIfNotNull(await CalculateTotalMovieCount()); list.AddIfNotNull(await CalculateTotalMovieGenres()); list.AddIfNotNull(CalculateTotalPlayLength()); @@ -131,12 +133,12 @@ private async Task>> CalculateCards() return list; } - private Task> CalculateTotalMovieCount() + private Task CalculateTotalMovieCount() { return CalculateStat(async () => { var count = await _movieRepository.Count(); - return new Card + return new Card { Title = Constants.Movies.TotalMovies, Value = count.ToString(), @@ -146,12 +148,12 @@ private Task> CalculateTotalMovieCount() }, "Calculate total movie count failed:"); } - private Task> CalculateTotalMovieGenres() + private Task CalculateTotalMovieGenres() { return CalculateStat(async () => { var totalGenres = await _movieRepository.GetGenreCount(); - return new Card + return new Card { Title = Constants.Common.TotalGenres, Value = totalGenres.ToString(), @@ -161,14 +163,14 @@ private Task> CalculateTotalMovieGenres() }, "Calculate total movie genres failed:"); } - private Card CalculateTotalPlayLength() + private Card CalculateTotalPlayLength() { return CalculateStat(() => { var playLengthTicks = _movieRepository.GetTotalRuntime() ?? 0; var playLength = new TimeSpan(playLengthTicks); - return new Card + return new Card { Title = Constants.Movies.TotalPlayLength, Value = $"{playLength.Days}|{playLength.Hours}|{playLength.Minutes}", @@ -178,12 +180,12 @@ private Card CalculateTotalPlayLength() }, "Calculate total movie play length failed:"); } - private Card CalculateTotalDiskSpace() + private Card CalculateTotalDiskSpace() { return CalculateStat(() => { var sum = _movieRepository.GetTotalDiskSpace(); - return new Card + return new Card { Value = sum.ToString(CultureInfo.InvariantCulture), Title = Constants.Common.TotalDiskSpace, @@ -193,12 +195,12 @@ private Card CalculateTotalDiskSpace() }, "Calculate total movie disk space failed:"); } - private Card TotalPersonTypeCount(PersonType type, string title) + private Card TotalPersonTypeCount(PersonType type, string title) { return CalculateStat(() => { var value = _movieRepository.GetPeopleCount(type); - return new Card + return new Card { Value = value.ToString(), Title = title, diff --git a/EmbyStat.Core/Movies/MovieStatistics.cs b/EmbyStat.Core/Movies/MovieStatistics.cs index ee1cfabba..3001622d5 100644 --- a/EmbyStat.Core/Movies/MovieStatistics.cs +++ b/EmbyStat.Core/Movies/MovieStatistics.cs @@ -5,7 +5,7 @@ namespace EmbyStat.Core.Movies; public class MovieStatistics { - public List> Cards { get; set; } + public List Cards { get; set; } public List TopCards { get; set; } public List Charts { get; set; } public IEnumerable Shorts { get; set; } diff --git a/EmbyStat.Core/People/PersonRepository.cs b/EmbyStat.Core/People/PersonRepository.cs index cd9b1fdc6..889a11678 100644 --- a/EmbyStat.Core/People/PersonRepository.cs +++ b/EmbyStat.Core/People/PersonRepository.cs @@ -30,6 +30,14 @@ public async Task UpsertRange(IEnumerable people) public async Task DeleteAll() { + await using var connection = _sqliteBootstrap.CreateConnection(); + await connection.OpenAsync(); + await using var transaction = connection.BeginTransaction(); + + var query = $"DELETE FROM {Constants.Tables.People}"; + await connection.ExecuteAsync(query, transaction); + await transaction.CommitAsync(); + _context.People.RemoveRange(_context.People); await _context.SaveChangesAsync(); } diff --git a/EmbyStat.Core/Sessions/Interfaces/ISessionRepository.cs b/EmbyStat.Core/Sessions/Interfaces/ISessionRepository.cs index 5b176b7ad..9fdfaa46e 100644 --- a/EmbyStat.Core/Sessions/Interfaces/ISessionRepository.cs +++ b/EmbyStat.Core/Sessions/Interfaces/ISessionRepository.cs @@ -5,11 +5,7 @@ namespace EmbyStat.Core.Sessions.Interfaces; public interface ISessionRepository { - bool DoesSessionExists(string sessionId); - void UpdateSession(Session session); - void CreateSession(Session session); - void InsertPlays(List sessionPlays); - IEnumerable GetMediaIdsForUser(string userId, PlayType type); + IEnumerable GetMediaIdsForUser(string userId, MediaType type); IEnumerable GetSessionsForUser(string userId); int GetPlayCountForUser(string userId); Session GetSessionById(string sessionId); diff --git a/EmbyStat.Core/Sessions/Interfaces/ISessionService.cs b/EmbyStat.Core/Sessions/Interfaces/ISessionService.cs index ad8a8ca85..afb82f043 100644 --- a/EmbyStat.Core/Sessions/Interfaces/ISessionService.cs +++ b/EmbyStat.Core/Sessions/Interfaces/ISessionService.cs @@ -5,7 +5,7 @@ namespace EmbyStat.Core.Sessions.Interfaces; public interface ISessionService { - IEnumerable GetMediaIdsForUser(string id, PlayType type); + IEnumerable GetMediaIdsForUser(string id, MediaType type); IEnumerable GetSessionsForUser(string id); int GetPlayCountForUser(string id); void ProcessSessions(List sessions); diff --git a/EmbyStat.Core/Sessions/SessionService.cs b/EmbyStat.Core/Sessions/SessionService.cs index 34d9ca28d..19df9b4ef 100644 --- a/EmbyStat.Core/Sessions/SessionService.cs +++ b/EmbyStat.Core/Sessions/SessionService.cs @@ -12,7 +12,7 @@ public SessionService() { } - public IEnumerable GetMediaIdsForUser(string id, PlayType type) + public IEnumerable GetMediaIdsForUser(string id, MediaType type) { throw new NotImplementedException(); // return _sessionRepository.GetMediaIdsForUser(id, type); diff --git a/EmbyStat.Core/Shows/Interfaces/IShowRepository.cs b/EmbyStat.Core/Shows/Interfaces/IShowRepository.cs index 7d4ee2eca..99f586754 100644 --- a/EmbyStat.Core/Shows/Interfaces/IShowRepository.cs +++ b/EmbyStat.Core/Shows/Interfaces/IShowRepository.cs @@ -19,6 +19,7 @@ public interface IShowRepository : IMediaRepository Task CompleteCollectedCount(); Task DeleteAll(); + Task RemoveUnwantedShows(IEnumerable libraryIds); #endregion #region Charts diff --git a/EmbyStat.Core/Shows/ShowRepository.cs b/EmbyStat.Core/Shows/ShowRepository.cs index 808065a11..9c64b98b5 100644 --- a/EmbyStat.Core/Shows/ShowRepository.cs +++ b/EmbyStat.Core/Shows/ShowRepository.cs @@ -226,7 +226,6 @@ public async Task UpsertShows(IEnumerable shows) foreach (var show in showList) { await using var transaction = connection.BeginTransaction(); - var showQuery = $@"INSERT OR REPLACE INTO {Constants.Tables.Shows} (Id,DateCreated,Banner,Logo,""Primary"",Thumb,Name,Path,PremiereDate,ProductionYear,SortName,CommunityRating,IMDB,TMDB,TVDB,RunTimeTicks,OfficialRating,CumulativeRunTimeTicks,Status,ExternalSynced,SizeInMb,LibraryId) @@ -475,6 +474,12 @@ public async Task DeleteAll() await _context.SaveChangesAsync(); } + public async Task RemoveUnwantedShows(IEnumerable libraryIds) + { + _context.Shows.RemoveRange(_context.Shows.Where(x => !libraryIds.Any(y => y == x.LibraryId))); + await _context.SaveChangesAsync(); + } + #endregion #region Episodes diff --git a/EmbyStat.Core/Shows/ShowService.cs b/EmbyStat.Core/Shows/ShowService.cs index 4d07b09e0..3ca737f4b 100644 --- a/EmbyStat.Core/Shows/ShowService.cs +++ b/EmbyStat.Core/Shows/ShowService.cs @@ -92,16 +92,18 @@ public Task GetShow(string id) return _showRepository.GetShowByIdWithEpisodes(id); } - public Task SetLibraryAsSynced(string[] libraryIds) + public async Task SetLibraryAsSynced(string[] libraryIds) { - return _mediaServerRepository.SetLibraryAsSynced(libraryIds, LibraryType.TvShow); + await _mediaServerRepository.SetLibraryAsSynced(libraryIds, LibraryType.TvShow); + await _showRepository.RemoveUnwantedShows(libraryIds); + await _statisticsRepository.MarkTypesAsInvalid(StatisticType.Show); } #region Cards - private async Task>> CalculateCards() + private async Task> CalculateCards() { - var list = new List>(); + var list = new List(); list.AddIfNotNull(await CalculateTotalShowCount()); list.AddIfNotNull(await CalculateCompleteCollectedShowCount()); list.AddIfNotNull(await CalculateTotalEpisodeCount()); @@ -114,13 +116,13 @@ private async Task>> CalculateCards() return list; } - private Task> CalculateTotalShowCount() + private Task CalculateTotalShowCount() { return CalculateStat(async () => { var count = await _showRepository.Count(); - return new Card + return new Card { Title = Constants.Shows.TotalShows, Value = count.ToString(), @@ -130,12 +132,12 @@ private Task> CalculateTotalShowCount() }, "Calculate total show count failed:"); } - private Task> CalculateCompleteCollectedShowCount() + private Task CalculateCompleteCollectedShowCount() { return CalculateStat(async () => { var count = await _showRepository.CompleteCollectedCount(); - return new Card + return new Card { Title = Constants.Shows.TotalCompleteCollectedShows, Value = count.ToString(), @@ -145,13 +147,13 @@ private Task> CalculateCompleteCollectedShowCount() }, "Calculate total completed collected show count failed:"); } - private Task> CalculateTotalEpisodeCount() + private Task CalculateTotalEpisodeCount() { return CalculateStat(async () => { var total = await _showRepository.GetEpisodeCount(LocationType.Disk); - return new Card + return new Card { Title = Constants.Shows.TotalEpisodes, Value = total.ToString(), @@ -161,12 +163,12 @@ private Task> CalculateTotalEpisodeCount() }, "Calculate total episode count failed:"); } - private Task> CalculateTotalShowGenres() + private Task CalculateTotalShowGenres() { return CalculateStat(async () => { var totalGenres = await _showRepository.GetGenreCount(); - return new Card + return new Card { Title = Constants.Common.TotalGenres, Value = totalGenres.ToString(), @@ -176,13 +178,13 @@ private Task> CalculateTotalShowGenres() }, "Calculate total show genres count failed:"); } - private Task> CalculateTotalMissingEpisodeCount() + private Task CalculateTotalMissingEpisodeCount() { return CalculateStat(async () => { var total = await _showRepository.GetEpisodeCount(LocationType.Virtual); - return new Card + return new Card { Title = Constants.Shows.TotalMissingEpisodes, Value = total.ToString(), @@ -192,14 +194,14 @@ private Task> CalculateTotalMissingEpisodeCount() }, "Calculate total missing episodes failed:"); } - private Task> CalculatePlayableTime() + private Task CalculatePlayableTime() { return CalculateStat(async () => { var totalRunTimeTicks = await _showRepository.GetTotalRunTimeTicks(); var playLength = new TimeSpan(totalRunTimeTicks); - return new Card + return new Card { Title = Constants.Shows.TotalPlayLength, Value = $"{playLength.Days}|{playLength.Hours}|{playLength.Minutes}", @@ -209,13 +211,13 @@ private Task> CalculatePlayableTime() }, "Calculate total playable time failed:"); } - private Task> CalculateTotalDiskSpace() + private Task CalculateTotalDiskSpace() { return CalculateStat(async () => { var total = await _showRepository.GetTotalDiskSpaceUsed(); - return new Card + return new Card { Value = total.ToString(CultureInfo.InvariantCulture), Title = Constants.Common.TotalDiskSpace, @@ -225,12 +227,12 @@ private Task> CalculateTotalDiskSpace() }, "Calculate total disk space failed:"); } - private Card TotalPersonTypeCount(PersonType type, string title) + private Card TotalPersonTypeCount(PersonType type, string title) { return CalculateStat(() => { var value = _showRepository.GetPeopleCount(type); - return new Card + return new Card { Value = value.ToString(), Title = title, diff --git a/EmbyStat.Core/Shows/ShowStatistics.cs b/EmbyStat.Core/Shows/ShowStatistics.cs index 28694732e..13676a406 100644 --- a/EmbyStat.Core/Shows/ShowStatistics.cs +++ b/EmbyStat.Core/Shows/ShowStatistics.cs @@ -5,7 +5,7 @@ namespace EmbyStat.Core.Shows; public class ShowStatistics { - public List> Cards { get; set; } + public List Cards { get; set; } public List TopCards { get; set; } public List BarCharts { get; set; } public List PieCharts { get; set; } diff --git a/EmbyStat.Core/System/SystemService.cs b/EmbyStat.Core/System/SystemService.cs index f9954c069..94566b27c 100644 --- a/EmbyStat.Core/System/SystemService.cs +++ b/EmbyStat.Core/System/SystemService.cs @@ -45,13 +45,6 @@ public async Task ResetEmbyStatTables() { await _hub.BroadcastResetLogLine("Deleting statistics"); await _statisticsRepository.DeleteStatistics(); - - await _hub.BroadcastResetLogLine("Deleting server info"); - await _mediaServerRepository.DeleteAllPlugins(); - await _mediaServerRepository.DeleteAllDevices(); - await _mediaServerRepository.DeleteServerInfo(); - await _mediaServerRepository.DeleteAllUsers(); - await _mediaServerRepository.DeleteAllLibraries(); await _hub.BroadcastResetLogLine("Deleting show data"); await _showRepository.DeleteAll(); @@ -67,6 +60,13 @@ public async Task ResetEmbyStatTables() await _hub.BroadcastResetLogLine("Deleting filters"); await _filterRepository.DeleteAll(); + + await _hub.BroadcastResetLogLine("Deleting server info"); + await _mediaServerRepository.DeleteAllPlugins(); + await _mediaServerRepository.DeleteAllDevices(); + await _mediaServerRepository.DeleteServerInfo(); + await _mediaServerRepository.DeleteAllUsers(); + await _mediaServerRepository.DeleteAllLibraries(); await _hub.BroadcastResetLogLine("Testing new media server info"); var config = _configurationService.Get(); diff --git a/EmbyStat.DI/DiExtensions.cs b/EmbyStat.DI/DiExtensions.cs index 1aa9d5394..bc029bd16 100644 --- a/EmbyStat.DI/DiExtensions.cs +++ b/EmbyStat.DI/DiExtensions.cs @@ -3,7 +3,6 @@ using EmbyStat.Clients.Base; using EmbyStat.Clients.Base.Api; using EmbyStat.Clients.Base.Http; -using EmbyStat.Clients.Base.Metadata; using EmbyStat.Clients.Base.WebSocket; using EmbyStat.Clients.Emby; using EmbyStat.Clients.Emby.Http; diff --git a/EmbyStat.Hosts.Cmd/DefaultConfig.cs b/EmbyStat.Hosts.Cmd/DefaultConfig.cs index e906bd9f0..8a89dd971 100644 --- a/EmbyStat.Hosts.Cmd/DefaultConfig.cs +++ b/EmbyStat.Hosts.Cmd/DefaultConfig.cs @@ -72,7 +72,7 @@ public static class DefaultConfig { ApiKey = "0ad9610e613fdbf0d62e71c96d903e0c", LastUpdate = null, - UseAsFallback = false + UseAsFallback = true } } }; diff --git a/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj b/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj index 43bbfb9fd..75bfa4a71 100644 --- a/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj +++ b/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj @@ -56,7 +56,6 @@ - diff --git a/EmbyStat.Hosts.Cmd/Program.cs b/EmbyStat.Hosts.Cmd/Program.cs index 6029a625e..1b27ac539 100644 --- a/EmbyStat.Hosts.Cmd/Program.cs +++ b/EmbyStat.Hosts.Cmd/Program.cs @@ -320,7 +320,9 @@ private static void SetupLogger(string logPath, int logLevel) .MinimumLevel.Override("Microsoft.AspNetCore.DataProtection", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.SignalR", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Server.Kestrel", LogEventLevel.Warning) - .MinimumLevel.Override("Microsoft.Microsoft.AspNetCore.Mvc.Infrastructure", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Mvc.ModelBinding", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Mvc.Infrastructure", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Cors", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication.JwtBearer", LogEventLevel.Warning) .MinimumLevel.Override("System.Net.Http.HttpClient", minimumLevel) diff --git a/EmbyStat.Hosts.Cmd/Startup.cs b/EmbyStat.Hosts.Cmd/Startup.cs index 1f7b1e1f1..0cdcb28a0 100644 --- a/EmbyStat.Hosts.Cmd/Startup.cs +++ b/EmbyStat.Hosts.Cmd/Startup.cs @@ -37,7 +37,6 @@ using Microsoft.OpenApi.Models; using Refit; using Rollbar.DTOs; -using ILogger = Microsoft.Extensions.Logging.ILogger; namespace EmbyStat.Hosts.Cmd; diff --git a/EmbyStat.Jobs/BaseJob.cs b/EmbyStat.Jobs/BaseJob.cs index 5478528c4..11cf36719 100644 --- a/EmbyStat.Jobs/BaseJob.cs +++ b/EmbyStat.Jobs/BaseJob.cs @@ -80,9 +80,7 @@ private async Task PreJobExecution() State = JobState.Running; await BroadcastProgress(0); - await LogInformation("---------------------------------"); - await LogInformation("Starting job"); - await LogInformation("---------------------------------"); + await LogInformation("-------------- Starting job --------------"); await _jobRepository.StartJob(Id); } @@ -94,12 +92,10 @@ private async Task PostJobExecution() await BroadcastProgress(100, now); var runTime = now.Subtract(StartTimeUtc ?? now).TotalMinutes; - await LogInformation("---------------------------------"); await LogInformation(Math.Abs(Math.Ceiling(runTime) - 1) < 0.1 ? "Job finished after 1 minute." : $"Job finished after {Math.Ceiling(runTime)} minutes."); await LogInformation("---------------------------------"); - } private async Task FailExecution() diff --git a/EmbyStat.Jobs/Jobs/Sync/MovieSyncJob.cs b/EmbyStat.Jobs/Jobs/Sync/MovieSyncJob.cs index 81687a3b5..de8a22012 100644 --- a/EmbyStat.Jobs/Jobs/Sync/MovieSyncJob.cs +++ b/EmbyStat.Jobs/Jobs/Sync/MovieSyncJob.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using EmbyStat.Clients.Base; using EmbyStat.Clients.Base.Http; using EmbyStat.Common; using EmbyStat.Common.Enums; using EmbyStat.Common.Extensions; +using EmbyStat.Common.Models.Entities; using EmbyStat.Common.Models.Entities.Movies; using EmbyStat.Configuration.Interfaces; using EmbyStat.Core.Filters.Interfaces; @@ -18,6 +20,7 @@ using EmbyStat.Jobs.Jobs.Interfaces; using Hangfire; using Microsoft.Extensions.Logging; +using MoreLinq.Extensions; namespace EmbyStat.Jobs.Jobs.Sync; @@ -103,7 +106,7 @@ private async Task ProcessPeopleAsync() private async Task ProcessMoviesAsync() { - var librariesToProcess = await _mediaServerRepository.GetAllLibraries(LibraryType.Movies, true); + var librariesToProcess = await _mediaServerRepository.GetAllSyncedLibraries(LibraryType.Movies); await LogInformation("Lets start processing movies"); await LogInformation($"{librariesToProcess.Count} libraries are selected, getting ready for processing"); @@ -112,35 +115,43 @@ private async Task ProcessMoviesAsync() foreach (var library in librariesToProcess) { - var totalCount = await _baseHttpClient.GetMediaCount(library.Id, library.LastSynced, "Movie"); - if (totalCount == 0) - { - continue; - } - + await LogInformation($"Processing {library.Name}"); + await ProcessChangedMovies(library, genres, logIncrementBase); + await _mediaServerRepository.UpdateLibrarySyncDate(library.Id, DateTime.UtcNow, LibraryType.Movies); + await LogInformation($"Processing {library.Name} done"); + } + } - await LogInformation($"Found {totalCount} changed movies since last sync in {library.Name}"); - var processed = 0; - var j = 0; - const int limit = 50; - - var increment = logIncrementBase / (totalCount / (double)limit); - do - { - var movies = await _baseHttpClient.GetMedia(library.Id, j * limit, limit, library.LastSynced, "Movie"); - - movies.AddGenres(genres); - await _movieRepository.UpsertRange(movies); - - processed += limit; - j++; - var logProcessed = processed < totalCount ? processed : totalCount; - await LogInformation($"Processed { logProcessed } / { totalCount } movies"); - await LogProgressIncrement(increment); - } while (processed < totalCount); - - await _mediaServerRepository.UpdateLibrarySyncDate(library.Id, DateTime.UtcNow); + private async Task ProcessChangedMovies(Library library, IReadOnlyList genres, double logIncrementBase) + { + var lastSynced = library.SyncTypes.GetLastSyncedDateForLibrary(library.Id, LibraryType.TvShow); + var totalCount = await _baseHttpClient.GetMediaCount(library.Id, lastSynced, "Movie"); + if (totalCount == 0) + { + await LogInformation("No changes detected to sync"); + return; } + + await LogInformation($"Found {totalCount} changed movies since last sync in {library.Name}"); + var processed = 0; + var j = 0; + const int limit = 50; + + var increment = logIncrementBase / (totalCount / (double)limit); + do + { + var movies = await _baseHttpClient.GetMedia(library.Id, j * limit, limit, lastSynced, "Movie"); + + movies.AddGenres(genres); + movies.ForEach(x => x.Library = library); + await _movieRepository.UpsertRange(movies); + + processed += limit; + j++; + var logProcessed = processed < totalCount ? processed : totalCount; + await LogInformation($"Processed { logProcessed } / { totalCount } movies"); + await LogProgressIncrement(increment); + } while (processed < totalCount); } private async Task CalculateStatistics() diff --git a/EmbyStat.Jobs/Jobs/Sync/ShowSyncJob.cs b/EmbyStat.Jobs/Jobs/Sync/ShowSyncJob.cs index 9ccffcf35..7f6cef37f 100644 --- a/EmbyStat.Jobs/Jobs/Sync/ShowSyncJob.cs +++ b/EmbyStat.Jobs/Jobs/Sync/ShowSyncJob.cs @@ -5,8 +5,6 @@ using EmbyStat.Clients.Base; using EmbyStat.Clients.Base.Http; using EmbyStat.Clients.Base.Metadata; -using EmbyStat.Clients.Tmdb; -using EmbyStat.Clients.TvMaze; using EmbyStat.Common; using EmbyStat.Common.Enums; using EmbyStat.Common.Exceptions; @@ -118,7 +116,7 @@ private async Task ProcessPeopleAsync() private async Task ProcessShowsAsync() { - var librariesToProcess = await _mediaServerRepository.GetAllLibraries(LibraryType.TvShow, true); + var librariesToProcess = await _mediaServerRepository.GetAllSyncedLibraries(LibraryType.TvShow); await LogInformation("Processing shows"); await LogInformation( $"{librariesToProcess.Count} libraries are selected, getting ready for processing"); @@ -128,17 +126,21 @@ await LogInformation( foreach (var library in librariesToProcess) { + await LogInformation($"Processing {library.Name}"); await ProcessChangedShows(library, genres, logIncrementBase); await ProcessFailedShows(library, genres, logIncrementBase); - await _mediaServerRepository.UpdateLibrarySyncDate(library.Id, DateTime.UtcNow); + await _mediaServerRepository.UpdateLibrarySyncDate(library.Id, DateTime.UtcNow, LibraryType.TvShow); + await LogInformation($"Processing {library.Name} done"); } } private async Task ProcessChangedShows(Library library, IEnumerable genres, double logIncrementBase) { - var totalCount = await _baseHttpClient.GetMediaCount(library.Id, library.LastSynced, "Series"); + var lastSynced = library.SyncTypes.GetLastSyncedDateForLibrary(library.Id, LibraryType.TvShow); + var totalCount = await _baseHttpClient.GetMediaCount(library.Id, lastSynced, "Series"); if (totalCount == 0) { + await LogInformation("No changes detected to sync"); return; } @@ -151,7 +153,7 @@ private async Task ProcessChangedShows(Library library, IEnumerable genre do { await LogInformation($"Fetching next block of {limit} shows..."); - var shows = await _baseHttpClient.GetShows(library.Id, j * limit, limit, library.LastSynced); + var shows = await _baseHttpClient.GetShows(library.Id, j * limit, limit, lastSynced); await ProcessShowBlock(library, shows, genres, increment); processed += limit; @@ -166,6 +168,13 @@ private async Task ProcessFailedShows(Library library, IEnumerable genres { var failedShowIds = _showRepository.GetShowIdsThatFailedExternalSync(library.Id).ToArray(); var totalCount = failedShowIds.Length; + + if (totalCount == 0) + { + await LogInformation($"No failed shows detected to resync"); + return; + } + var increment = logIncrementBase / totalCount; await LogInformation($"Found {totalCount} failed shows, trying to sync them again."); diff --git a/EmbyStat.Migrations/EmbyStat.Migrations.csproj b/EmbyStat.Migrations/EmbyStat.Migrations.csproj deleted file mode 100644 index c582fb9c5..000000000 --- a/EmbyStat.Migrations/EmbyStat.Migrations.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net6.0 - - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD} - true - - - - - - - - - - - - - - - diff --git a/EmbyStat.Migrations/Sqlite/20220510134722_Init.cs b/EmbyStat.Migrations/Sqlite/20220510134722_Init.cs deleted file mode 100644 index 0aa2408ec..000000000 --- a/EmbyStat.Migrations/Sqlite/20220510134722_Init.cs +++ /dev/null @@ -1,1149 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -namespace EmbyStat.Migrations.Sqlite -{ - public partial class Init : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Devices", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - LastUserName = table.Column(type: "TEXT", nullable: true), - AppName = table.Column(type: "TEXT", nullable: true), - AppVersion = table.Column(type: "TEXT", nullable: true), - LastUserId = table.Column(type: "TEXT", nullable: true), - DateLastActivity = table.Column(type: "TEXT", nullable: true), - IconUrl = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Devices", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Filters", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Field = table.Column(type: "TEXT", nullable: true), - Type = table.Column(type: "INTEGER", nullable: false), - Values = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Filters", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Genres", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Genres", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Jobs", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - State = table.Column(type: "INTEGER", nullable: false), - CurrentProgressPercentage = table.Column(type: "REAL", nullable: true), - StartTimeUtc = table.Column(type: "TEXT", nullable: true), - EndTimeUtc = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - Description = table.Column(type: "TEXT", nullable: true), - Trigger = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Jobs", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Languages", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - Code = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Languages", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Libraries", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true), - Type = table.Column(type: "INTEGER", nullable: false), - Sync = table.Column(type: "INTEGER", nullable: false), - LastSynced = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Libraries", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "MediaServerInfo", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - SystemUpdateLevel = table.Column(type: "TEXT", nullable: true), - OperatingSystemDisplayName = table.Column(type: "TEXT", nullable: true), - HasPendingRestart = table.Column(type: "INTEGER", nullable: false), - SupportsLibraryMonitor = table.Column(type: "INTEGER", nullable: false), - WebSocketPortNumber = table.Column(type: "INTEGER", nullable: false), - CanSelfRestart = table.Column(type: "INTEGER", nullable: false), - CanSelfUpdate = table.Column(type: "INTEGER", nullable: false), - CanLaunchWebBrowser = table.Column(type: "INTEGER", nullable: false), - ProgramDataPath = table.Column(type: "TEXT", nullable: true), - ItemsByNamePath = table.Column(type: "TEXT", nullable: true), - CachePath = table.Column(type: "TEXT", nullable: true), - LogPath = table.Column(type: "TEXT", nullable: true), - InternalMetadataPath = table.Column(type: "TEXT", nullable: true), - TranscodingTempPath = table.Column(type: "TEXT", nullable: true), - HttpServerPortNumber = table.Column(type: "INTEGER", nullable: false), - SupportsHttps = table.Column(type: "INTEGER", nullable: false), - HttpsPortNumber = table.Column(type: "INTEGER", nullable: false), - HasUpdateAvailable = table.Column(type: "INTEGER", nullable: false), - SupportsAutoRunAtStartup = table.Column(type: "INTEGER", nullable: false), - HardwareAccelerationRequiresPremiere = table.Column(type: "INTEGER", nullable: false), - LocalAddress = table.Column(type: "TEXT", nullable: true), - WanAddress = table.Column(type: "TEXT", nullable: true), - ServerName = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "TEXT", nullable: true), - OperatingSystem = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaServerInfo", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "MediaServerStatus", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - MissedPings = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaServerStatus", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "MediaServerUsers", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - ServerId = table.Column(type: "TEXT", nullable: true), - HasPassword = table.Column(type: "INTEGER", nullable: false), - HasConfiguredPassword = table.Column(type: "INTEGER", nullable: false), - HasConfiguredEasyPassword = table.Column(type: "INTEGER", nullable: false), - PrimaryImageTag = table.Column(type: "TEXT", nullable: true), - LastLoginDate = table.Column(type: "TEXT", nullable: true), - LastActivityDate = table.Column(type: "TEXT", nullable: true), - PlayDefaultAudioTrack = table.Column(type: "INTEGER", nullable: false), - SubtitleLanguagePreference = table.Column(type: "TEXT", nullable: true), - DisplayMissingEpisodes = table.Column(type: "INTEGER", nullable: false), - SubtitleMode = table.Column(type: "TEXT", nullable: true), - IsAdministrator = table.Column(type: "INTEGER", nullable: false), - IsHidden = table.Column(type: "INTEGER", nullable: false), - IsHiddenRemotely = table.Column(type: "INTEGER", nullable: false), - IsHiddenFromUnusedDevices = table.Column(type: "INTEGER", nullable: false), - IsDisabled = table.Column(type: "INTEGER", nullable: false), - EnableLiveTvAccess = table.Column(type: "INTEGER", nullable: false), - EnableContentDeletion = table.Column(type: "INTEGER", nullable: false), - EnableContentDownloading = table.Column(type: "INTEGER", nullable: false), - EnableSubtitleDownloading = table.Column(type: "INTEGER", nullable: false), - EnableSubtitleManagement = table.Column(type: "INTEGER", nullable: false), - EnableSyncTranscoding = table.Column(type: "INTEGER", nullable: false), - EnableMediaConversion = table.Column(type: "INTEGER", nullable: false), - InvalidLoginAttemptCount = table.Column(type: "INTEGER", nullable: false), - EnablePublicSharing = table.Column(type: "INTEGER", nullable: false), - RemoteClientBitrateLimit = table.Column(type: "INTEGER", nullable: false), - SimultaneousStreamLimit = table.Column(type: "INTEGER", nullable: false), - EnableAllDevices = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaServerUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "People", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_People", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Plugins", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - Version = table.Column(type: "TEXT", nullable: true), - Description = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Plugins", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "RoleClaims", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - RoleId = table.Column(type: "TEXT", nullable: true), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_RoleClaims", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Roles", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: true), - NormalizedName = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Roles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "Statistics", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - CalculationDateTime = table.Column(type: "TEXT", nullable: false), - Type = table.Column(type: "INTEGER", nullable: false), - JsonResult = table.Column(type: "TEXT", nullable: true), - IsValid = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Statistics", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UserClaims", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - UserId = table.Column(type: "TEXT", nullable: true), - ClaimType = table.Column(type: "TEXT", nullable: true), - ClaimValue = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_UserClaims", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "TEXT", nullable: false), - ProviderKey = table.Column(type: "TEXT", nullable: false), - ProviderDisplayName = table.Column(type: "TEXT", nullable: true), - UserId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_UserLogins", x => new { x.LoginProvider, x.ProviderKey }); - }); - - migrationBuilder.CreateTable( - name: "UserRoles", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - RoleId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.RoleId }); - }); - - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - RefreshTokens = table.Column(type: "TEXT", nullable: true), - UserName = table.Column(type: "TEXT", nullable: true), - NormalizedUserName = table.Column(type: "TEXT", nullable: true), - Email = table.Column(type: "TEXT", nullable: true), - NormalizedEmail = table.Column(type: "TEXT", nullable: true), - EmailConfirmed = table.Column(type: "INTEGER", nullable: false), - PasswordHash = table.Column(type: "TEXT", nullable: true), - SecurityStamp = table.Column(type: "TEXT", nullable: true), - ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), - PhoneNumber = table.Column(type: "TEXT", nullable: true), - PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), - TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), - LockoutEnd = table.Column(type: "TEXT", nullable: true), - LockoutEnabled = table.Column(type: "INTEGER", nullable: false), - AccessFailedCount = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "UserTokens", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - LoginProvider = table.Column(type: "TEXT", nullable: false), - Name = table.Column(type: "TEXT", nullable: false), - Value = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_UserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - }); - - migrationBuilder.CreateTable( - name: "Movies", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - OriginalTitle = table.Column(type: "TEXT", nullable: true), - LibraryId = table.Column(type: "TEXT", nullable: true), - DateCreated = table.Column(type: "TEXT", nullable: true), - Banner = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - PremiereDate = table.Column(type: "TEXT", nullable: true), - ProductionYear = table.Column(type: "INTEGER", nullable: true), - SortName = table.Column(type: "TEXT", nullable: true), - CommunityRating = table.Column(type: "TEXT", nullable: true), - IMDB = table.Column(type: "TEXT", nullable: true), - TMDB = table.Column(type: "INTEGER", nullable: true), - TVDB = table.Column(type: "TEXT", nullable: true), - RunTimeTicks = table.Column(type: "INTEGER", nullable: true), - OfficialRating = table.Column(type: "TEXT", nullable: true), - Container = table.Column(type: "TEXT", nullable: true), - Video3DFormat = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Movies", x => x.Id); - table.ForeignKey( - name: "FK_Movies_Libraries_LibraryId", - column: x => x.LibraryId, - principalTable: "Libraries", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "Shows", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - CumulativeRunTimeTicks = table.Column(type: "INTEGER", nullable: true), - Status = table.Column(type: "TEXT", nullable: true), - ExternalSynced = table.Column(type: "INTEGER", nullable: false), - SizeInMb = table.Column(type: "REAL", nullable: false), - LibraryId = table.Column(type: "TEXT", nullable: true), - DateCreated = table.Column(type: "TEXT", nullable: true), - Banner = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - PremiereDate = table.Column(type: "TEXT", nullable: true), - ProductionYear = table.Column(type: "INTEGER", nullable: true), - SortName = table.Column(type: "TEXT", nullable: true), - CommunityRating = table.Column(type: "TEXT", nullable: true), - IMDB = table.Column(type: "TEXT", nullable: true), - TMDB = table.Column(type: "INTEGER", nullable: true), - TVDB = table.Column(type: "TEXT", nullable: true), - RunTimeTicks = table.Column(type: "INTEGER", nullable: true), - OfficialRating = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Shows", x => x.Id); - table.ForeignKey( - name: "FK_Shows_Libraries_LibraryId", - column: x => x.LibraryId, - principalTable: "Libraries", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "GenreMovie", - columns: table => new - { - GenresId = table.Column(type: "TEXT", nullable: false), - MoviesId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_GenreMovie", x => new { x.GenresId, x.MoviesId }); - table.ForeignKey( - name: "FK_GenreMovie_Genres_GenresId", - column: x => x.GenresId, - principalTable: "Genres", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_GenreMovie_Movies_MoviesId", - column: x => x.MoviesId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "GenreShow", - columns: table => new - { - GenresId = table.Column(type: "TEXT", nullable: false), - ShowsId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_GenreShow", x => new { x.GenresId, x.ShowsId }); - table.ForeignKey( - name: "FK_GenreShow_Genres_GenresId", - column: x => x.GenresId, - principalTable: "Genres", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_GenreShow_Shows_ShowsId", - column: x => x.ShowsId, - principalTable: "Shows", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MediaPerson", - columns: table => new - { - Id = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Type = table.Column(type: "INTEGER", nullable: false), - MovieId = table.Column(type: "TEXT", nullable: true), - ShowId = table.Column(type: "TEXT", nullable: true), - PersonId = table.Column(type: "TEXT", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaPerson", x => x.Id); - table.ForeignKey( - name: "FK_MediaPerson_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MediaPerson_People_PersonId", - column: x => x.PersonId, - principalTable: "People", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MediaPerson_Shows_ShowId", - column: x => x.ShowId, - principalTable: "Shows", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Season", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - IndexNumber = table.Column(type: "INTEGER", nullable: true), - IndexNumberEnd = table.Column(type: "INTEGER", nullable: true), - LocationType = table.Column(type: "INTEGER", nullable: false), - ShowId = table.Column(type: "TEXT", nullable: false), - DateCreated = table.Column(type: "TEXT", nullable: true), - Banner = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - PremiereDate = table.Column(type: "TEXT", nullable: true), - ProductionYear = table.Column(type: "INTEGER", nullable: true), - SortName = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_Season", x => x.Id); - table.ForeignKey( - name: "FK_Season_Shows_ShowId", - column: x => x.ShowId, - principalTable: "Shows", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "Episodes", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - DvdEpisodeNumber = table.Column(type: "REAL", nullable: true), - DvdSeasonNumber = table.Column(type: "INTEGER", nullable: true), - IndexNumber = table.Column(type: "INTEGER", nullable: true), - IndexNumberEnd = table.Column(type: "INTEGER", nullable: true), - LocationType = table.Column(type: "INTEGER", nullable: false), - SeasonId = table.Column(type: "TEXT", nullable: false), - DateCreated = table.Column(type: "TEXT", nullable: true), - Banner = table.Column(type: "TEXT", nullable: true), - Logo = table.Column(type: "TEXT", nullable: true), - Primary = table.Column(type: "TEXT", nullable: true), - Thumb = table.Column(type: "TEXT", nullable: true), - Name = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - PremiereDate = table.Column(type: "TEXT", nullable: true), - ProductionYear = table.Column(type: "INTEGER", nullable: true), - SortName = table.Column(type: "TEXT", nullable: true), - CommunityRating = table.Column(type: "TEXT", nullable: true), - IMDB = table.Column(type: "TEXT", nullable: true), - TMDB = table.Column(type: "INTEGER", nullable: true), - TVDB = table.Column(type: "TEXT", nullable: true), - RunTimeTicks = table.Column(type: "INTEGER", nullable: true), - OfficialRating = table.Column(type: "TEXT", nullable: true), - Container = table.Column(type: "TEXT", nullable: true), - Video3DFormat = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Episodes", x => x.Id); - table.ForeignKey( - name: "FK_Episodes_Season_SeasonId", - column: x => x.SeasonId, - principalTable: "Season", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AudioStream", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - BitRate = table.Column(type: "INTEGER", nullable: true), - ChannelLayout = table.Column(type: "TEXT", nullable: true), - Channels = table.Column(type: "INTEGER", nullable: true), - Codec = table.Column(type: "TEXT", nullable: true), - Language = table.Column(type: "TEXT", nullable: true), - SampleRate = table.Column(type: "INTEGER", nullable: true), - IsDefault = table.Column(type: "INTEGER", nullable: false), - MovieId = table.Column(type: "TEXT", nullable: true), - EpisodeId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AudioStream", x => x.Id); - table.ForeignKey( - name: "FK_AudioStream_Episodes_EpisodeId", - column: x => x.EpisodeId, - principalTable: "Episodes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AudioStream_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "MediaServerUserViews", - columns: table => new - { - UserId = table.Column(type: "TEXT", nullable: false), - MediaId = table.Column(type: "TEXT", nullable: false), - MediaType = table.Column(type: "TEXT", nullable: true), - PlayCount = table.Column(type: "INTEGER", nullable: false), - LastPlayedDate = table.Column(type: "TEXT", nullable: false), - EpisodeId = table.Column(type: "TEXT", nullable: true), - MovieId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaServerUserViews", x => new { x.UserId, x.MediaId }); - table.ForeignKey( - name: "FK_MediaServerUserViews_Episodes_EpisodeId", - column: x => x.EpisodeId, - principalTable: "Episodes", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_MediaServerUserViews_MediaServerUsers_UserId", - column: x => x.UserId, - principalTable: "MediaServerUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MediaServerUserViews_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id"); - }); - - migrationBuilder.CreateTable( - name: "MediaSource", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - BitRate = table.Column(type: "INTEGER", nullable: true), - Container = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - Protocol = table.Column(type: "TEXT", nullable: true), - RunTimeTicks = table.Column(type: "INTEGER", nullable: true), - SizeInMb = table.Column(type: "REAL", nullable: false), - MovieId = table.Column(type: "TEXT", nullable: true), - EpisodeId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_MediaSource", x => x.Id); - table.ForeignKey( - name: "FK_MediaSource_Episodes_EpisodeId", - column: x => x.EpisodeId, - principalTable: "Episodes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_MediaSource_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "SubtitleStream", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - Codec = table.Column(type: "TEXT", nullable: true), - DisplayTitle = table.Column(type: "TEXT", nullable: true), - IsDefault = table.Column(type: "INTEGER", nullable: false), - Language = table.Column(type: "TEXT", nullable: true), - MovieId = table.Column(type: "TEXT", nullable: true), - EpisodeId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_SubtitleStream", x => x.Id); - table.ForeignKey( - name: "FK_SubtitleStream_Episodes_EpisodeId", - column: x => x.EpisodeId, - principalTable: "Episodes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_SubtitleStream_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "VideoStream", - columns: table => new - { - Id = table.Column(type: "TEXT", nullable: false), - AspectRatio = table.Column(type: "TEXT", nullable: true), - AverageFrameRate = table.Column(type: "REAL", nullable: true), - BitRate = table.Column(type: "INTEGER", nullable: true), - Channels = table.Column(type: "INTEGER", nullable: true), - Height = table.Column(type: "INTEGER", nullable: true), - Language = table.Column(type: "TEXT", nullable: true), - Width = table.Column(type: "INTEGER", nullable: true), - BitDepth = table.Column(type: "INTEGER", nullable: true), - Codec = table.Column(type: "TEXT", nullable: true), - IsDefault = table.Column(type: "INTEGER", nullable: false), - VideoRange = table.Column(type: "TEXT", nullable: true), - MovieId = table.Column(type: "TEXT", nullable: true), - EpisodeId = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_VideoStream", x => x.Id); - table.ForeignKey( - name: "FK_VideoStream_Episodes_EpisodeId", - column: x => x.EpisodeId, - principalTable: "Episodes", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_VideoStream_Movies_MovieId", - column: x => x.MovieId, - principalTable: "Movies", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("41e0bf22-1e6b-4f5d-90be-ec966f746a2f"), 0.0, "SYSTEM-SYNCDESCRIPTION", null, null, 0, "SYSTEM-SYNC", "0 2 * * *" }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("78bc2bf0-abd9-48ef-aeff-9c396d644f2a"), 0.0, "UPDATE-CHECKERDESCRIPTION", null, null, 0, "UPDATE-CHECKER", "0 */12 * * *" }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("b109ca73-0563-4062-a3e2-f7e6a00b73e9"), 0.0, "DATABASE-CLEANUPDESCRIPTION", null, null, 0, "DATABASE-CLEANUP", "0 4 * * *" }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("be68900b-ee1d-41ef-b12f-60ef3106052e"), 0.0, "SHOW-SYNCDESCRIPTION", null, null, 0, "SHOW-SYNC", "0 3 * * *" }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("c40555dc-ea57-4c6e-a225-905223d31c3c"), 0.0, "MOVIE-SYNCDESCRIPTION", null, null, 0, "MOVIE-SYNC", "0 2 * * *" }); - - migrationBuilder.InsertData( - table: "Jobs", - columns: new[] { "Id", "CurrentProgressPercentage", "Description", "EndTimeUtc", "StartTimeUtc", "State", "Title", "Trigger" }, - values: new object[] { new Guid("ce1fbc9e-21ee-450b-9cdf-58a0e17ea98e"), 0.0, "PINGDESCRIPTION", null, null, 0, "PING", "*/5 * * * *" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "082d8aaf-f86a-4401-bf0f-c315b3c9d904", "nl-NL", "Nederlands" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "282182b9-9332-4266-a093-5ff5b7f927a9", "it-IT", "Italiano" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "3e8d27e9-e314-4d57-967f-cf5d84144acf", "sv-SE", "Svenska" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "490c1cb5-b711-4514-aa97-d22ddff2b2fa", "pt-PT", "Português" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "62fe1b5e-3328-450b-b24b-fa16bea58870", "en-US", "English" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "6b103b14-20d1-49c0-b7ce-8d701399b64d", "ro-RO", "Românesc" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "91cca672-af55-4d55-899a-798826a43773", "fi-FI", "Suomi" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "97616a9b-60f9-407a-9a87-b4518da5e5f4", "zh-CN", "简体中文" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "99142c2f-379e-4a25-879b-ecfe25ee9e7c", "fr-FR", "Français" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "a48a2ef9-3b64-4069-8e31-252abb6d07a3", "hu-HU", "Magyar" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "b21074db-74b9-4e24-8867-34e82c265256", "pt-BR", "Brasileiro" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "c0a60a3b-282e-46f5-aa7f-661c88f2edb0", "es-ES", "Español" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "c9df5e5c-75f3-46c2-8095-97fde33531d8", "de-DE", "Deutsch" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "d6401f6b-becf-49e2-b82e-1018b3bf607f", "el-GR", "Ελληνικά" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "d8b0ae7b-9ba7-4a51-9d7c-94402b51265d", "no-NO", "Norsk" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "e1940bfd-00c4-46f9-b9ac-a87a5d92e8ca", "da-DK", "Dansk" }); - - migrationBuilder.InsertData( - table: "Languages", - columns: new[] { "Id", "Code", "Name" }, - values: new object[] { "f3966f43-3ec6-456e-850f-a2ebfc0b539b", "pl-PL", "Polski" }); - - migrationBuilder.InsertData( - table: "MediaServerStatus", - columns: new[] { "Id", "MissedPings" }, - values: new object[] { new Guid("e55668a1-6a81-47ba-882d-738347e7e9cd"), 0 }); - - migrationBuilder.CreateIndex( - name: "IX_AudioStream_EpisodeId", - table: "AudioStream", - column: "EpisodeId"); - - migrationBuilder.CreateIndex( - name: "IX_AudioStream_MovieId", - table: "AudioStream", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_Episodes_SeasonId", - table: "Episodes", - column: "SeasonId"); - - migrationBuilder.CreateIndex( - name: "IX_GenreMovie_MoviesId", - table: "GenreMovie", - column: "MoviesId"); - - migrationBuilder.CreateIndex( - name: "IX_Genres_Name", - table: "Genres", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_GenreShow_ShowsId", - table: "GenreShow", - column: "ShowsId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaPerson_MovieId", - table: "MediaPerson", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaPerson_PersonId", - table: "MediaPerson", - column: "PersonId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaPerson_ShowId", - table: "MediaPerson", - column: "ShowId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaServerUserViews_EpisodeId", - table: "MediaServerUserViews", - column: "EpisodeId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaServerUserViews_MovieId", - table: "MediaServerUserViews", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaSource_EpisodeId", - table: "MediaSource", - column: "EpisodeId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaSource_MovieId", - table: "MediaSource", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_MediaSource_SizeInMb", - table: "MediaSource", - column: "SizeInMb"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_CommunityRating", - table: "Movies", - column: "CommunityRating"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_LibraryId", - table: "Movies", - column: "LibraryId"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_Logo", - table: "Movies", - column: "Logo"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_Name", - table: "Movies", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_Primary", - table: "Movies", - column: "Primary"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_RunTimeTicks", - table: "Movies", - column: "RunTimeTicks"); - - migrationBuilder.CreateIndex( - name: "IX_Movies_SortName", - table: "Movies", - column: "SortName"); - - migrationBuilder.CreateIndex( - name: "IX_Season_ShowId", - table: "Season", - column: "ShowId"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_CommunityRating", - table: "Shows", - column: "CommunityRating"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_LibraryId", - table: "Shows", - column: "LibraryId"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_Logo", - table: "Shows", - column: "Logo"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_Name", - table: "Shows", - column: "Name"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_Primary", - table: "Shows", - column: "Primary"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_RunTimeTicks", - table: "Shows", - column: "RunTimeTicks"); - - migrationBuilder.CreateIndex( - name: "IX_Shows_SortName", - table: "Shows", - column: "SortName"); - - migrationBuilder.CreateIndex( - name: "IX_SubtitleStream_EpisodeId", - table: "SubtitleStream", - column: "EpisodeId"); - - migrationBuilder.CreateIndex( - name: "IX_SubtitleStream_Language", - table: "SubtitleStream", - column: "Language"); - - migrationBuilder.CreateIndex( - name: "IX_SubtitleStream_MovieId", - table: "SubtitleStream", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_AverageFrameRate", - table: "VideoStream", - column: "AverageFrameRate"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_BitDepth", - table: "VideoStream", - column: "BitDepth"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_Codec", - table: "VideoStream", - column: "Codec"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_EpisodeId", - table: "VideoStream", - column: "EpisodeId"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_Height", - table: "VideoStream", - column: "Height"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_MovieId", - table: "VideoStream", - column: "MovieId"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_VideoRange", - table: "VideoStream", - column: "VideoRange"); - - migrationBuilder.CreateIndex( - name: "IX_VideoStream_Width", - table: "VideoStream", - column: "Width"); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AudioStream"); - - migrationBuilder.DropTable( - name: "Devices"); - - migrationBuilder.DropTable( - name: "Filters"); - - migrationBuilder.DropTable( - name: "GenreMovie"); - - migrationBuilder.DropTable( - name: "GenreShow"); - - migrationBuilder.DropTable( - name: "Jobs"); - - migrationBuilder.DropTable( - name: "Languages"); - - migrationBuilder.DropTable( - name: "MediaPerson"); - - migrationBuilder.DropTable( - name: "MediaServerInfo"); - - migrationBuilder.DropTable( - name: "MediaServerStatus"); - - migrationBuilder.DropTable( - name: "MediaServerUserViews"); - - migrationBuilder.DropTable( - name: "MediaSource"); - - migrationBuilder.DropTable( - name: "Plugins"); - - migrationBuilder.DropTable( - name: "RoleClaims"); - - migrationBuilder.DropTable( - name: "Roles"); - - migrationBuilder.DropTable( - name: "Statistics"); - - migrationBuilder.DropTable( - name: "SubtitleStream"); - - migrationBuilder.DropTable( - name: "UserClaims"); - - migrationBuilder.DropTable( - name: "UserLogins"); - - migrationBuilder.DropTable( - name: "UserRoles"); - - migrationBuilder.DropTable( - name: "Users"); - - migrationBuilder.DropTable( - name: "UserTokens"); - - migrationBuilder.DropTable( - name: "VideoStream"); - - migrationBuilder.DropTable( - name: "Genres"); - - migrationBuilder.DropTable( - name: "People"); - - migrationBuilder.DropTable( - name: "MediaServerUsers"); - - migrationBuilder.DropTable( - name: "Episodes"); - - migrationBuilder.DropTable( - name: "Movies"); - - migrationBuilder.DropTable( - name: "Season"); - - migrationBuilder.DropTable( - name: "Shows"); - - migrationBuilder.DropTable( - name: "Libraries"); - } - } -} diff --git a/EmbyStat.Web/ClientApp/public/locales/base.json b/EmbyStat.Web/ClientApp/public/locales/base.json index b035364f4..80e31ac9d 100644 --- a/EmbyStat.Web/ClientApp/public/locales/base.json +++ b/EmbyStat.Web/ClientApp/public/locales/base.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,13 +345,14 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { "EPISODES": "Episodes watched", "MOVIES": "Movies watched", "TOTAL": "Total watched" - } + } }, "WIZARD": { "ACCOUNT": "User details", diff --git a/EmbyStat.Web/ClientApp/src/i18n.tsx b/EmbyStat.Web/ClientApp/src/i18n.tsx index e68ad5858..db9dd6e80 100644 --- a/EmbyStat.Web/ClientApp/src/i18n.tsx +++ b/EmbyStat.Web/ClientApp/src/i18n.tsx @@ -3,10 +3,10 @@ import LanguageDetector from 'i18next-browser-languagedetector'; import HttpApi from 'i18next-http-backend'; import {initReactI18next} from 'react-i18next'; -const translationFilePath = '/locales/{{lng}}.json'; -// if (process.env.NODE_ENV === 'development' && module.hot) { -// translationFilePath = '/locales/base.json'; -// } +let translationFilePath = '/locales/{{lng}}.json'; +if (process.env.NODE_ENV === 'development' && module.hot) { + translationFilePath = '/locales/base.json'; +} i18n .use(HttpApi) diff --git a/EmbyStat.Web/ClientApp/src/pages/server/components/EsServerDetails.tsx b/EmbyStat.Web/ClientApp/src/pages/server/components/EsServerDetails.tsx index 3d7eb5446..67b4c9c60 100644 --- a/EmbyStat.Web/ClientApp/src/pages/server/components/EsServerDetails.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/server/components/EsServerDetails.tsx @@ -39,7 +39,7 @@ export function EsServerDetails() { Media server logo { userConfig.language = data.language; + setSaving(true); await save(userConfig); + setSaving(false); }; return ( @@ -44,6 +47,7 @@ export function EsLanguageCard() { title={t('SETTINGS.LANGUAGE.TITLE')} saveForm={saveForm} handleSubmit={handleSubmit} + saving={saving} > diff --git a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsPasswordCard.tsx b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsPasswordCard.tsx index 24581e8aa..aac490104 100644 --- a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsPasswordCard.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsPasswordCard.tsx @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import React, {useContext, useState} from 'react'; import {useForm} from 'react-hook-form'; import {useTranslation} from 'react-i18next'; @@ -17,12 +17,15 @@ type Password = { export function EsPasswordCard() { const {changePassword, user, login} = useContext(UserContext); + const [saving, setSaving] = useState(false); const {t} = useTranslation(); const submitForm = async (data: Password) => { if (user !== null) { + setSaving(true); const result = await changePassword(data.password, data.currentPassword); + setSaving(false); if (result) { await login(user.username, data.password); SnackbarUtils.success(t('SETTINGS.PASSWORD.PASSWORDUPDATED')); @@ -53,6 +56,7 @@ export function EsPasswordCard() { title='SETTINGS.PASSWORD.TITLE' saveForm={submitForm} handleSubmit={handleSubmit} + saving={saving} > {t('SETTINGS.PASSWORD.CONTENT')} diff --git a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsRollbarCard.tsx b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsRollbarCard.tsx index 5ef362c47..20bde7210 100644 --- a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsRollbarCard.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsRollbarCard.tsx @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import React, {useContext, useState} from 'react'; import {Controller, useForm} from 'react-hook-form'; import {useTranslation} from 'react-i18next'; @@ -14,10 +14,13 @@ type RollbarForm = { export function EsRollbarCard() { const {userConfig, save} = useContext(SettingsContext); const {t} = useTranslation(); + const [saving, setSaving] = useState(false); const saveForm = async (data: RollbarForm) => { userConfig.enableRollbarLogging = data.enabled; + setSaving(true); await save(userConfig); + setSaving(false); }; const {handleSubmit, control} = useForm({ @@ -36,6 +39,7 @@ export function EsRollbarCard() { title='SETTINGS.ROLLBAR.TITLE' saveForm={saveForm} handleSubmit={handleSubmit} + saving={saving} > {t('SETTINGS.ROLLBAR.EXCEPTIONLOGGING')} diff --git a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsTmdbApiCard.tsx b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsTmdbApiCard.tsx index 7ec63dc54..e0e1369c9 100644 --- a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsTmdbApiCard.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsTmdbApiCard.tsx @@ -1,5 +1,5 @@ import {t} from 'i18next'; -import React, {useContext} from 'react'; +import React, {useContext, useState} from 'react'; import {Controller, useForm} from 'react-hook-form'; import {Checkbox, FormControlLabel, Typography} from '@mui/material'; @@ -15,6 +15,7 @@ type TmdbApiForm = { export function EsTmdbApiCard() { const {userConfig, save} = useContext(SettingsContext); + const [saving, setSaving] = useState(false); const {handleSubmit, register, getValues, formState: {errors}, control} = useForm({ mode: 'all', @@ -27,7 +28,10 @@ export function EsTmdbApiCard() { const saveForm = async (data: TmdbApiForm) => { userConfig.tmdb.apiKey = data.apiKey; userConfig.tmdb.useAsFallback = data.enableFallback; + + setSaving(true); await save(userConfig); + setSaving(false); }; const apiKeyRegister = register('apiKey', {required: true}); @@ -37,6 +41,7 @@ export function EsTmdbApiCard() { title={t('SETTINGS.TMDB.TITLE')} saveForm={saveForm} handleSubmit={handleSubmit} + saving={saving} > {t('SETTINGS.TMDB.APIKEYWARNING')} diff --git a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsUserCard.tsx b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsUserCard.tsx index cd794e259..03f810069 100644 --- a/EmbyStat.Web/ClientApp/src/pages/settings/components/EsUserCard.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/settings/components/EsUserCard.tsx @@ -1,4 +1,4 @@ -import React, {useContext} from 'react'; +import React, {useContext, useState} from 'react'; import {useForm} from 'react-hook-form'; import {useTranslation} from 'react-i18next'; @@ -16,6 +16,7 @@ type User = { export function EsUserCard() { const {user, changeUserName} = useContext(UserContext); const {t} = useTranslation(); + const [saving, setSaving] = useState(false); const {register, handleSubmit, getValues, formState: {errors}} = useForm({ mode: 'all', @@ -26,7 +27,10 @@ export function EsUserCard() { const submitForm = async (data: User) => { if (user !== null) { + setSaving(true); const result = await changeUserName(data.username); + + setSaving(false); if (result) { SnackbarUtils.success(t('SETTINGS.ACCOUNT.USERNAMEUPDATED')); return; @@ -42,6 +46,7 @@ export function EsUserCard() { title='SETTINGS.ACCOUNT.USERNAME' saveForm={submitForm} handleSubmit={handleSubmit} + saving={saving} > {t('SETTINGS.ACCOUNT.CONTENT')} diff --git a/EmbyStat.Web/ClientApp/src/pages/users/components/EsUserTable.tsx b/EmbyStat.Web/ClientApp/src/pages/users/components/EsUserTable.tsx index 6f4429a5d..38e18f712 100644 --- a/EmbyStat.Web/ClientApp/src/pages/users/components/EsUserTable.tsx +++ b/EmbyStat.Web/ClientApp/src/pages/users/components/EsUserTable.tsx @@ -1,13 +1,13 @@ import {format} from 'date-fns'; import React, {useEffect, useState} from 'react'; +import {useNavigate} from 'react-router'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; -import { - Avatar, Box, Paper, Table, TableBody, TableCell, TableContainer, TableRow, -} from '@mui/material'; +import {Box, Paper, Table, TableBody, TableCell, TableContainer, TableRow} from '@mui/material'; -import {EsHyperLinkButton} from '../../../shared/components/buttons'; +import {EsButton, EsHyperLinkButton} from '../../../shared/components/buttons'; +import {EsAvatar} from '../../../shared/components/esAvatar'; import {EsTableHeader, EsTablePagination, Header} from '../../../shared/components/table'; import {useLocale, useMediaServerUrls, useServerType} from '../../../shared/hooks'; import {MediaServerUserRow} from '../../../shared/models/mediaServer'; @@ -19,28 +19,19 @@ type EsUserRowProps = { function EsUserRow(props: EsUserRowProps) { const {user} = props; - const {getPrimaryUserImageLink} = useMediaServerUrls(); const {locale} = useLocale(); + const navigate = useNavigate(); const {serverType} = useServerType(); const {getUserDetailLink} = useMediaServerUrls(); - const [backgroundColor, setBackgroundColor] = useState('#000000'); - useEffect(() => { - setBackgroundColor('#' + user.id.substring(9, 6)); - }, [user.id]); + const openUser = () => { + navigate(`/users/${user.id}`); + }; return ( - + - theme.palette.getContrastText(backgroundColor), - }} - src={getPrimaryUserImageLink(user.id)} - /> + {user.name} {format(new Date(user.lastActivityDate ?? ''), 'p P', {locale})} @@ -60,6 +51,13 @@ function EsUserRow(props: EsUserRowProps) { {!user.isDisabled && ()} + + { <>Details} + diff --git a/EmbyStat.Web/ClientApp/src/pages/users/hooks/useUserDetails.tsx b/EmbyStat.Web/ClientApp/src/pages/users/hooks/useUserDetails.tsx new file mode 100644 index 000000000..9b9e36062 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/hooks/useUserDetails.tsx @@ -0,0 +1,22 @@ +import {useState} from 'react'; + +import {MediaServerUserDetails} from '../../../shared/models/mediaServer'; +import {getUserDetails} from '../../../shared/services'; + +export const useUserDetails = () => { + const [loading, setLoading] = useState(true); + const [userDetails, setUserDetails] = useState(null!); + + const fetchUserDetails = async (id: string | undefined) => { + if (id !== undefined) { + setLoading(true); + const user = await getUserDetails(id); + setUserDetails(user); + setLoading(false); + } + }; + + return { + loading, userDetails, fetchUserDetails, + }; +}; diff --git a/EmbyStat.Web/ClientApp/src/pages/users/subpages/UserDetails.tsx b/EmbyStat.Web/ClientApp/src/pages/users/subpages/UserDetails.tsx new file mode 100644 index 000000000..91f17125e --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/subpages/UserDetails.tsx @@ -0,0 +1,35 @@ +import React, {useEffect} from 'react'; +import {useParams} from 'react-router'; + +import {Grid, Stack} from '@mui/material'; + +import {EsLoading} from '../../../shared/components/esLoading'; +import {useUserDetails} from '../hooks/useUserDetails'; +import {User} from './components'; +import {Header} from './components/Header'; + +type Props = {} + +export function UserDetails(props: Props) { + const {id} = useParams(); + const {loading, userDetails, fetchUserDetails} = useUserDetails(); + + useEffect(() => { + (async () => { + await fetchUserDetails(id); + })(); + }, [id]); + + return ( + + +
+ + + + + + + + ); +} diff --git a/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/Header.tsx b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/Header.tsx new file mode 100644 index 000000000..2a692c80e --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/Header.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +import {Box, Card, Stack} from '@mui/material'; + +import {EsAvatar} from '../../../../shared/components/esAvatar'; +import {EsTitle} from '../../../../shared/components/esTitle'; +import {MediaServerUserDetails} from '../../../../shared/models/mediaServer'; + +type Props = { + details: MediaServerUserDetails +} + +export function Header(props: Props) { + const {details} = props; + return ( + + + + + + + + + + + ); +} diff --git a/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/User.tsx b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/User.tsx new file mode 100644 index 000000000..668ec7e09 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/User.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import {useTranslation} from 'react-i18next'; + +import { + Card, CardContent, Stack, Table, TableBody, TableCell, TableRow, Typography, +} from '@mui/material'; + +import {EsBoolCheck} from '../../../../shared/components/esBoolCheck'; +import {EsDateOrNever} from '../../../../shared/components/esDateOrNever'; +import {EsTitle} from '../../../../shared/components/esTitle'; +import {MediaServerUserDetails} from '../../../../shared/models/mediaServer'; + +type Props = { + details: MediaServerUserDetails +} + +export function User(props: Props) { + const {details} = props; + const {t} = useTranslation(); + + const dataRows = [ + {label: 'USERS.ADMIN', value: }, + {label: 'USERS.DISABLED', value: }, + {label: 'USERS.HIDDEN', value: }, + {label: 'USERS.LASTACTIVE', value: }, + {label: 'USERS.LASTLOGIN', value: }, + ]; + + return ( + + + + profile + + + + + { + dataRows.map((row) => ( + + + {t(row.label)} + + {row.value} + + )) + } + +
+
+
+
+
+ ); +} diff --git a/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/index.tsx b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/index.tsx new file mode 100644 index 000000000..f6b9f36c6 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/subpages/components/index.tsx @@ -0,0 +1 @@ +export * from './User'; diff --git a/EmbyStat.Web/ClientApp/src/pages/users/subpages/index.tsx b/EmbyStat.Web/ClientApp/src/pages/users/subpages/index.tsx new file mode 100644 index 000000000..534017782 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/pages/users/subpages/index.tsx @@ -0,0 +1 @@ +export * from './UserDetails'; diff --git a/EmbyStat.Web/ClientApp/src/routes.tsx b/EmbyStat.Web/ClientApp/src/routes.tsx index e5aff2752..a850144d8 100644 --- a/EmbyStat.Web/ClientApp/src/routes.tsx +++ b/EmbyStat.Web/ClientApp/src/routes.tsx @@ -15,6 +15,7 @@ import { } from './pages/settings'; import {General as ShowGeneral, List as ShowList, Shows} from './pages/shows'; import {Users} from './pages/users'; +import {UserDetails} from './pages/users/subpages'; import Wizard from './pages/wizard'; import {SettingsContext} from './shared/context/settings'; @@ -52,6 +53,7 @@ function RoutesContainer() { } /> } /> + } /> } /> } /> } /> diff --git a/EmbyStat.Web/ClientApp/src/shared/components/buttons/EsButton.tsx b/EmbyStat.Web/ClientApp/src/shared/components/buttons/EsButton.tsx index eeff22827..e9f22bf12 100644 --- a/EmbyStat.Web/ClientApp/src/shared/components/buttons/EsButton.tsx +++ b/EmbyStat.Web/ClientApp/src/shared/components/buttons/EsButton.tsx @@ -1,6 +1,7 @@ -import {Button} from '@mui/material'; import React, {ReactElement} from 'react'; +import {Button} from '@mui/material'; + type Props = { disabled?: boolean; onClick: ((event: React.MouseEvent) => void) | undefined; @@ -8,6 +9,7 @@ type Props = { fullWidth?: boolean; variant?: 'text'| 'contained'; color?: 'primary' |'secondary'; + type?: 'submit' | 'button'; } export function EsButton(props: Props) { @@ -18,11 +20,12 @@ export function EsButton(props: Props) { onClick, variant = 'contained', color = 'primary', + type = 'submit', } = props; return ( diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/EsAvatar.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/EsAvatar.tsx new file mode 100644 index 000000000..76b2a3d7a --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/EsAvatar.tsx @@ -0,0 +1,35 @@ +import React, {useEffect, useState} from 'react'; + +import {Avatar} from '@mui/material'; + +import {useMediaServerUrls} from '../../hooks'; + +type Props = { + name: string + id: string, + borderRight?: boolean +} + +export function EsAvatar(props: Props) { + const {name, id, borderRight = true} = props; + const {getPrimaryUserImageLink} = useMediaServerUrls(); + const [backgroundColor, setBackgroundColor] = useState('#000000'); + + useEffect(() => { + setBackgroundColor('#' + id.substring(9, 6)); + }, [id]); + + return ( + theme.palette.getContrastText(backgroundColor), + }} + src={getPrimaryUserImageLink(id)} + /> + ); +} diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/index.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/index.tsx new file mode 100644 index 000000000..8b4b507ab --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esAvatar/index.tsx @@ -0,0 +1 @@ +export * from './EsAvatar'; diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/EsBoolCheck.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/EsBoolCheck.tsx new file mode 100644 index 000000000..44d8e5607 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/EsBoolCheck.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; +import HighlightOffIcon from '@mui/icons-material/HighlightOff'; + +type Props = { + value: boolean +} + +export function EsBoolCheck(props: Props) { + const {value} = props; + + return value ? + : + ; +} diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/index.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/index.tsx new file mode 100644 index 000000000..0d65aad56 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esBoolCheck/index.tsx @@ -0,0 +1 @@ +export * from './EsBoolCheck'; diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/EsDateOrNever.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/EsDateOrNever.tsx new file mode 100644 index 000000000..2452e0823 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/EsDateOrNever.tsx @@ -0,0 +1,22 @@ +import {format} from 'date-fns'; +import React from 'react'; +import {useTranslation} from 'react-i18next'; + +import Typography from '@mui/material/Typography'; + +import {useLocale} from '../../hooks'; + +type Props = { + value: string | null +} + +export function EsDateOrNever(props: Props) { + const {value} = props; + const {locale} = useLocale(); + const {t} = useTranslation(); + + return (value !== null ? + {format(new Date(value), 'Pp', {locale})} : + {t('COMMON.NEVER')} + ); +} diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/index.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/index.tsx new file mode 100644 index 000000000..811af91e7 --- /dev/null +++ b/EmbyStat.Web/ClientApp/src/shared/components/esDateOrNever/index.tsx @@ -0,0 +1 @@ +export * from './EsDateOrNever'; diff --git a/EmbyStat.Web/ClientApp/src/shared/components/esLibrarySelector/EsLibrarySelector.tsx b/EmbyStat.Web/ClientApp/src/shared/components/esLibrarySelector/EsLibrarySelector.tsx index 85a47c218..a512fc7cd 100644 --- a/EmbyStat.Web/ClientApp/src/shared/components/esLibrarySelector/EsLibrarySelector.tsx +++ b/EmbyStat.Web/ClientApp/src/shared/components/esLibrarySelector/EsLibrarySelector.tsx @@ -78,6 +78,7 @@ function EsLibrarySelectorContainer(props: Props) { const {fetch, type, push} = props; const {load, libraries, save} = useContext(LibrariesContext); const {handleSubmit} = useForm(); + const [saving, setSaving] = useState(false); const {t} = useTranslation(); useEffect(() => { @@ -87,7 +88,9 @@ function EsLibrarySelectorContainer(props: Props) { }, [fetch]); const submitForm = async () => { + setSaving(true); await save(push); + setSaving(false); }; return ( @@ -95,6 +98,7 @@ function EsLibrarySelectorContainer(props: Props) { title='SETTINGS.LIBRARIES.TITLE' saveForm={submitForm} handleSubmit={handleSubmit} + saving={saving} > {t('SETTINGS.LIBRARIES.CONTENT', {type: t(type)})} diff --git a/EmbyStat.Web/ClientApp/src/shared/models/library/Library.tsx b/EmbyStat.Web/ClientApp/src/shared/models/library/Library.tsx index 6479809e8..71ed0b83f 100644 --- a/EmbyStat.Web/ClientApp/src/shared/models/library/Library.tsx +++ b/EmbyStat.Web/ClientApp/src/shared/models/library/Library.tsx @@ -3,5 +3,6 @@ export interface Library { name: string; primary: string; type: number; + syncType: number; sync: boolean; } diff --git a/EmbyStat.Web/ClientApp/src/shared/models/mediaServer/MediaServerUser.tsx b/EmbyStat.Web/ClientApp/src/shared/models/mediaServer/MediaServerUser.tsx index d212cf59d..e2c6678b0 100644 --- a/EmbyStat.Web/ClientApp/src/shared/models/mediaServer/MediaServerUser.tsx +++ b/EmbyStat.Web/ClientApp/src/shared/models/mediaServer/MediaServerUser.tsx @@ -1,5 +1,36 @@ +import {Card} from '../common'; + export interface MediaServerUser { id: string; name: string; isAdministrator: boolean; } + +export interface MediaServerUserDetails extends MediaServerUser { + id: string; + name: string; + primaryImageTag: string; + lastLoginDate: string | null; + lastActivityDate: string | null; + subtitleLanguagePreference: string; + serverId: string; + isAdministrator: boolean; + isHidden: boolean; + isHiddenRemotely: boolean; + isHiddenFromUnusedDevices: boolean; + isDisabled: boolean; + enableLiveTvAccess: boolean; + enableContentDeletion: boolean; + enableContentDownloading: boolean; + nableSubtitleDownloading: boolean; + enableSubtitleManagement: boolean; + enableSyncTranscoding: boolean; + enableMediaConversion: boolean; + invalidLoginAttemptCount: number; + enablePublicSharing: boolean; + remoteClientBitrateLimit: number; + simultaneousStreamLimit: number; + enableAllDevices: boolean; + viewedMovieCount: Card; + viewedEpisodeCount: Card; +} diff --git a/EmbyStat.Web/ClientApp/src/shared/services/serverService.tsx b/EmbyStat.Web/ClientApp/src/shared/services/serverService.tsx index 7d2e41e9c..d944628d6 100644 --- a/EmbyStat.Web/ClientApp/src/shared/services/serverService.tsx +++ b/EmbyStat.Web/ClientApp/src/shared/services/serverService.tsx @@ -4,7 +4,7 @@ import {TablePage} from '../models/common'; import {Library} from '../models/library'; import { MediaServerInfo, MediaServerLogin, MediaServerPlugin, MediaServerUdpBroadcast, MediaServerUser, - MediaServerUserRow, MediaServerUserStatistics, + MediaServerUserDetails, MediaServerUserRow, MediaServerUserStatistics, } from '../models/mediaServer'; import {axiosInstance} from './axiosInstance'; @@ -80,6 +80,12 @@ export const getUserPage = (skip: number, take: number, sortField: string, .then((response) => response.data); }; +export const getUserDetails = (id: string): Promise => { + return axiosInstance + .get(`${domain}users/${id}`) + .then((response) => response.data); +}; + export const getUserStatistics = (): Promise => { return axiosInstance .get(`${domain}users/statistics`) diff --git a/EmbyStat.sln b/EmbyStat.sln index 5f4b08046..08e568807 100644 --- a/EmbyStat.sln +++ b/EmbyStat.sln @@ -29,8 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmbyStat.Clients.Jellyfin", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmbyStat.Clients.Tmdb", "EmbyStat.Clients.Tmdb\EmbyStat.Clients.Tmdb.csproj", "{4D486D10-E1D4-478A-BDFE-B600FEAC0A92}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EmbyStat.Migrations", "EmbyStat.Migrations\EmbyStat.Migrations.csproj", "{B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbyStat.Core", "EmbyStat.Core\EmbyStat.Core.csproj", "{9B5790D1-6CAC-4592-B338-85D8549B4DC2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbyStat.Configuration", "EmbyStat.Configuration\EmbyStat.Configuration.csproj", "{6829F708-CA6A-4EB8-99F1-27D1B64FA7EE}" @@ -290,24 +288,6 @@ Global {4D486D10-E1D4-478A-BDFE-B600FEAC0A92}.Release|x64.Build.0 = Release|Any CPU {4D486D10-E1D4-478A-BDFE-B600FEAC0A92}.Release|x86.ActiveCfg = Release|Any CPU {4D486D10-E1D4-478A-BDFE-B600FEAC0A92}.Release|x86.Build.0 = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|x64.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|x64.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|x86.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Debug|x86.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|x64.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.DefaultBuild|x86.Build.0 = Debug|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|Any CPU.Build.0 = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|x64.ActiveCfg = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|x64.Build.0 = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|x86.ActiveCfg = Release|Any CPU - {B3D26C6D-13F2-4E90-9DA9-2F636204C3AD}.Release|x86.Build.0 = Release|Any CPU {9B5790D1-6CAC-4592-B338-85D8549B4DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9B5790D1-6CAC-4592-B338-85D8549B4DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU {9B5790D1-6CAC-4592-B338-85D8549B4DC2}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/Tests.Unit/Services/MediaServerServiceTests.cs b/Tests.Unit/Services/MediaServerServiceTests.cs index c34606400..34c60843f 100644 --- a/Tests.Unit/Services/MediaServerServiceTests.cs +++ b/Tests.Unit/Services/MediaServerServiceTests.cs @@ -583,13 +583,13 @@ public async Task GetAndProcessLibraries_Should_Process_Libraries() { var libraries = new List { - new() {Id = "1", Sync = true}, + new() {Id = "1", SyncType = LibraryType.Movies}, new() {Id = "2"} }.ToArray(); _mediaServerRepositoryMock .Setup(x => x.GetAllLibraries()) - .ReturnsAsync(new List {new() {Id = "1", Sync = true}}); + .ReturnsAsync(new List {new() {Id = "1", SyncType = LibraryType.Movies}}); var strategy = new Mock(); _httpClientMock.Setup(x => x.GetLibraries()) From 61163f6412e23148a2f05b0f7d0882bd0bfdf44e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 14:43:28 +0200 Subject: [PATCH 02/20] Updating NuGet packages --- .../EmbyStat.Clients.TvMaze.csproj | 2 +- EmbyStat.Common/EmbyStat.Common.csproj | 8 ++++---- EmbyStat.Controllers/EmbyStat.Controllers.csproj | 4 ++-- EmbyStat.Core/EmbyStat.Core.csproj | 6 +++--- EmbyStat.DI/EmbyStat.DI.csproj | 2 +- EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj | 14 +++++++------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/EmbyStat.Clients.TvMaze/EmbyStat.Clients.TvMaze.csproj b/EmbyStat.Clients.TvMaze/EmbyStat.Clients.TvMaze.csproj index 7ec11cfdc..ecf7148de 100644 --- a/EmbyStat.Clients.TvMaze/EmbyStat.Clients.TvMaze.csproj +++ b/EmbyStat.Clients.TvMaze/EmbyStat.Clients.TvMaze.csproj @@ -7,7 +7,7 @@ - + diff --git a/EmbyStat.Common/EmbyStat.Common.csproj b/EmbyStat.Common/EmbyStat.Common.csproj index 393c6f844..a8ca4a33e 100644 --- a/EmbyStat.Common/EmbyStat.Common.csproj +++ b/EmbyStat.Common/EmbyStat.Common.csproj @@ -22,15 +22,15 @@ - - - + + + - + diff --git a/EmbyStat.Controllers/EmbyStat.Controllers.csproj b/EmbyStat.Controllers/EmbyStat.Controllers.csproj index 7ea0e935a..88d8f7d61 100644 --- a/EmbyStat.Controllers/EmbyStat.Controllers.csproj +++ b/EmbyStat.Controllers/EmbyStat.Controllers.csproj @@ -11,11 +11,11 @@ - + - + diff --git a/EmbyStat.Core/EmbyStat.Core.csproj b/EmbyStat.Core/EmbyStat.Core.csproj index 20b4feea3..397d6b02f 100644 --- a/EmbyStat.Core/EmbyStat.Core.csproj +++ b/EmbyStat.Core/EmbyStat.Core.csproj @@ -10,13 +10,13 @@ - + - - + + diff --git a/EmbyStat.DI/EmbyStat.DI.csproj b/EmbyStat.DI/EmbyStat.DI.csproj index 0e30deb2e..17dcddbb7 100644 --- a/EmbyStat.DI/EmbyStat.DI.csproj +++ b/EmbyStat.DI/EmbyStat.DI.csproj @@ -25,7 +25,7 @@ - + diff --git a/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj b/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj index 75bfa4a71..55befd712 100644 --- a/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj +++ b/EmbyStat.Hosts.Cmd/EmbyStat.Hosts.Cmd.csproj @@ -30,24 +30,24 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + From aeea8cab2083d3ce4e4390d66908e60a5bb75393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 14:43:36 +0200 Subject: [PATCH 03/20] Fixing unit tests --- Tests.Unit/Builders/LibraryBuilder.cs | 15 +++++++-- .../Controllers/MovieControllerTests.cs | 32 ++----------------- .../Services/MediaServerServiceTests.cs | 4 +-- Tests.Unit/Services/MovieServiceTests.cs | 1 + Tests.Unit/Services/ShowServiceTests.cs | 1 + 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/Tests.Unit/Builders/LibraryBuilder.cs b/Tests.Unit/Builders/LibraryBuilder.cs index b280a1c91..49491e0d0 100644 --- a/Tests.Unit/Builders/LibraryBuilder.cs +++ b/Tests.Unit/Builders/LibraryBuilder.cs @@ -1,4 +1,6 @@ -using EmbyStat.Common.Enums; +using System; +using System.Collections.Generic; +using EmbyStat.Common.Enums; using EmbyStat.Common.Models.Entities; namespace Tests.Unit.Builders; @@ -14,7 +16,16 @@ public LibraryBuilder(int index, LibraryType type) Id = $"id{index}", Name = $"collection{index}", Primary = $"image{index}", - Type = type + Type = type, + SyncTypes = new List + { + new() + { + Id = Guid.NewGuid().ToString(), + SyncType = type, + LibraryId = $"id{index}", + } + } }; } diff --git a/Tests.Unit/Controllers/MovieControllerTests.cs b/Tests.Unit/Controllers/MovieControllerTests.cs index 00c1df47d..a8f6da04c 100644 --- a/Tests.Unit/Controllers/MovieControllerTests.cs +++ b/Tests.Unit/Controllers/MovieControllerTests.cs @@ -52,36 +52,8 @@ public MovieControllerTests() _movieServiceMock.Setup(x => x.GetMovie(It.IsAny())) .ReturnsAsync(_movie); - var mapperMock = new Mock(); - mapperMock - .Setup(x => x.Map(It.IsAny())) - .Returns(new MovieStatisticsViewModel - {TopCards = new List {new() {Title = "The lord of the rings"}}}); - mapperMock - .Setup(x => x.Map>(It.IsAny>())) - .Returns( - new List - { - new() - { - Name = "collection1", - Primary = "image1", - Type = (int) LibraryType.Movies - }, - new() - { - Name = "collection2", - Primary = "image2", - Type = (int) LibraryType.Movies - } - }); - mapperMock - .Setup(x => x.Map(It.IsAny())) - .Returns(new MovieViewModel - { - Id = "1" - }); - _subject = new MovieController(_movieServiceMock.Object, mapperMock.Object); + var mapper = CreateMapper(); + _subject = new MovieController(_movieServiceMock.Object, mapper); } [Fact] diff --git a/Tests.Unit/Services/MediaServerServiceTests.cs b/Tests.Unit/Services/MediaServerServiceTests.cs index 34c60843f..3fbc3bd9b 100644 --- a/Tests.Unit/Services/MediaServerServiceTests.cs +++ b/Tests.Unit/Services/MediaServerServiceTests.cs @@ -583,13 +583,13 @@ public async Task GetAndProcessLibraries_Should_Process_Libraries() { var libraries = new List { - new() {Id = "1", SyncType = LibraryType.Movies}, + new() {Id = "1"}, new() {Id = "2"} }.ToArray(); _mediaServerRepositoryMock .Setup(x => x.GetAllLibraries()) - .ReturnsAsync(new List {new() {Id = "1", SyncType = LibraryType.Movies}}); + .ReturnsAsync(new List {new() {Id = "1"}}); var strategy = new Mock(); _httpClientMock.Setup(x => x.GetLibraries()) diff --git a/Tests.Unit/Services/MovieServiceTests.cs b/Tests.Unit/Services/MovieServiceTests.cs index 914c50d23..15b544a25 100644 --- a/Tests.Unit/Services/MovieServiceTests.cs +++ b/Tests.Unit/Services/MovieServiceTests.cs @@ -266,6 +266,7 @@ public async Task SetLibraryAsSynced_Should_Update_Libraries() _mediaServerRepositoryMock.Verify(x => x.SetLibraryAsSynced(list, LibraryType.Movies)); _mediaServerRepositoryMock.VerifyNoOtherCalls(); + _movieRepositoryMock.Verify(x => x.RemoveUnwantedMovies(list)); _movieRepositoryMock.VerifyNoOtherCalls(); } diff --git a/Tests.Unit/Services/ShowServiceTests.cs b/Tests.Unit/Services/ShowServiceTests.cs index 693971ba6..b0e26073a 100644 --- a/Tests.Unit/Services/ShowServiceTests.cs +++ b/Tests.Unit/Services/ShowServiceTests.cs @@ -215,6 +215,7 @@ public async Task SetLibraryAsSynced_Should_Update_Libraries() _mediaServerRepositoryMock.Verify(x => x.SetLibraryAsSynced(list, LibraryType.TvShow)); _mediaServerRepositoryMock.VerifyNoOtherCalls(); + _showRepositoryMock.Verify(x => x.RemoveUnwantedShows(list)); _showRepositoryMock.VerifyNoOtherCalls(); } From d11384e778c3436e715f8518a8a9a06eba72df55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:50 +0200 Subject: [PATCH 04/20] New translations base.json (Romanian) --- EmbyStat.Web/ClientApp/public/locales/ro-RO.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/ro-RO.json b/EmbyStat.Web/ClientApp/public/locales/ro-RO.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/ro-RO.json +++ b/EmbyStat.Web/ClientApp/public/locales/ro-RO.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From 04a3163c6271ea7bcaf83c7352a740cc6cb3b8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:51 +0200 Subject: [PATCH 05/20] New translations base.json (Dutch) --- EmbyStat.Web/ClientApp/public/locales/nl-NL.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/nl-NL.json b/EmbyStat.Web/ClientApp/public/locales/nl-NL.json index 9af423eb8..41d1e3564 100644 --- a/EmbyStat.Web/ClientApp/public/locales/nl-NL.json +++ b/EmbyStat.Web/ClientApp/public/locales/nl-NL.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update niveau", "VERSION": "Versie", "VIDEORANGE": "Videobereik", - "WIDTH": "Breedte" + "WIDTH": "Breedte", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Laatst actief", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Gebruikers", "WATCHED": { From e5f214b8cd6efc54b32943b90f0ec1c89d0d01db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:52 +0200 Subject: [PATCH 06/20] New translations base.json (English) --- EmbyStat.Web/ClientApp/public/locales/en-US.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/en-US.json b/EmbyStat.Web/ClientApp/public/locales/en-US.json index 799d1d084..965021dcb 100644 --- a/EmbyStat.Web/ClientApp/public/locales/en-US.json +++ b/EmbyStat.Web/ClientApp/public/locales/en-US.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update channel", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From 5dfdd81ab66e1fea6ba1540aca9d99182cc3492c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:53 +0200 Subject: [PATCH 07/20] New translations base.json (Chinese Simplified) --- EmbyStat.Web/ClientApp/public/locales/zh-CN.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/zh-CN.json b/EmbyStat.Web/ClientApp/public/locales/zh-CN.json index 19793db1d..a4862d323 100644 --- a/EmbyStat.Web/ClientApp/public/locales/zh-CN.json +++ b/EmbyStat.Web/ClientApp/public/locales/zh-CN.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "更新等级", "VERSION": "版本", "VIDEORANGE": "视频范围", - "WIDTH": "宽" + "WIDTH": "宽", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "上次登录:", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "用户", "WATCHED": { From 511f1886327d6759e742097684bb879d6a005b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:54 +0200 Subject: [PATCH 08/20] New translations base.json (Swedish) --- EmbyStat.Web/ClientApp/public/locales/sv-SE.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/sv-SE.json b/EmbyStat.Web/ClientApp/public/locales/sv-SE.json index 3cf618efe..8a1fd80d3 100644 --- a/EmbyStat.Web/ClientApp/public/locales/sv-SE.json +++ b/EmbyStat.Web/ClientApp/public/locales/sv-SE.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Längd på video", - "WIDTH": "Bredd" + "WIDTH": "Bredd", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Senast aktiv", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Användare", "WATCHED": { From b07d6deef57eda8cfb9e32a4321dc786a46c1f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:55 +0200 Subject: [PATCH 09/20] New translations base.json (Portuguese) --- EmbyStat.Web/ClientApp/public/locales/pt-PT.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/pt-PT.json b/EmbyStat.Web/ClientApp/public/locales/pt-PT.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/pt-PT.json +++ b/EmbyStat.Web/ClientApp/public/locales/pt-PT.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From e4e363d6c62e9465199b12d01bce73abaae81b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:56 +0200 Subject: [PATCH 10/20] New translations base.json (Polish) --- EmbyStat.Web/ClientApp/public/locales/pl-PL.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/pl-PL.json b/EmbyStat.Web/ClientApp/public/locales/pl-PL.json index 09be8bbfc..a3e5dc15c 100644 --- a/EmbyStat.Web/ClientApp/public/locales/pl-PL.json +++ b/EmbyStat.Web/ClientApp/public/locales/pl-PL.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Kanał aktualizacji", "VERSION": "Version", "VIDEORANGE": "Zakres wideo", - "WIDTH": "Szerokość" + "WIDTH": "Szerokość", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From e48a6e67ff69e1d0a2b0930bb832a2c618bca7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:57 +0200 Subject: [PATCH 11/20] New translations base.json (Norwegian) --- EmbyStat.Web/ClientApp/public/locales/no-NO.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/no-NO.json b/EmbyStat.Web/ClientApp/public/locales/no-NO.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/no-NO.json +++ b/EmbyStat.Web/ClientApp/public/locales/no-NO.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From 0e2c3aa005d0aaf4f1ad64f7e53330d0e2d6ba18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:58 +0200 Subject: [PATCH 12/20] New translations base.json (Italian) --- EmbyStat.Web/ClientApp/public/locales/it-IT.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/it-IT.json b/EmbyStat.Web/ClientApp/public/locales/it-IT.json index e036da6e5..9f79790cb 100644 --- a/EmbyStat.Web/ClientApp/public/locales/it-IT.json +++ b/EmbyStat.Web/ClientApp/public/locales/it-IT.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Livello di aggiornamento", "VERSION": "Versione", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Ultima attività", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Utenti", "WATCHED": { From 0272adb60543a3ee4f6c3449fe059d5954b29966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:17:59 +0200 Subject: [PATCH 13/20] New translations base.json (French) --- EmbyStat.Web/ClientApp/public/locales/fr-FR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/fr-FR.json b/EmbyStat.Web/ClientApp/public/locales/fr-FR.json index 54912b8cc..f0774b0e2 100644 --- a/EmbyStat.Web/ClientApp/public/locales/fr-FR.json +++ b/EmbyStat.Web/ClientApp/public/locales/fr-FR.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Canal de distribution", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Largeur" + "WIDTH": "Largeur", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Dernière activité", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Utilisateurs", "WATCHED": { From 2d60dfe2f285ec6d66e25c09b9b3cbb5485f0e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:00 +0200 Subject: [PATCH 14/20] New translations base.json (Hungarian) --- EmbyStat.Web/ClientApp/public/locales/hu-HU.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/hu-HU.json b/EmbyStat.Web/ClientApp/public/locales/hu-HU.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/hu-HU.json +++ b/EmbyStat.Web/ClientApp/public/locales/hu-HU.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From 5b33f2c1b523d6c50e927126bf7132901e64d876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:01 +0200 Subject: [PATCH 15/20] New translations base.json (Finnish) --- EmbyStat.Web/ClientApp/public/locales/fi-FI.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/fi-FI.json b/EmbyStat.Web/ClientApp/public/locales/fi-FI.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/fi-FI.json +++ b/EmbyStat.Web/ClientApp/public/locales/fi-FI.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From e9a30a2fb44c4604052866faa78d52c30f390340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:02 +0200 Subject: [PATCH 16/20] New translations base.json (Greek) --- EmbyStat.Web/ClientApp/public/locales/el-GR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/el-GR.json b/EmbyStat.Web/ClientApp/public/locales/el-GR.json index 7e2fe1bc1..c8da16505 100644 --- a/EmbyStat.Web/ClientApp/public/locales/el-GR.json +++ b/EmbyStat.Web/ClientApp/public/locales/el-GR.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Last active", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Users", "WATCHED": { From 8412f2bcfd85d4885f59b7da14b2afe7506bc646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:03 +0200 Subject: [PATCH 17/20] New translations base.json (German) --- EmbyStat.Web/ClientApp/public/locales/de-DE.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/de-DE.json b/EmbyStat.Web/ClientApp/public/locales/de-DE.json index 7bd37197d..8e8f34d96 100644 --- a/EmbyStat.Web/ClientApp/public/locales/de-DE.json +++ b/EmbyStat.Web/ClientApp/public/locales/de-DE.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update-Kanal", "VERSION": "Version", "VIDEORANGE": "Videobereich", - "WIDTH": "Breite" + "WIDTH": "Breite", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Zuletzt aktiv", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Benutzer", "WATCHED": { From 6fb7396c2251e1bee1c1a095b4d3056fbd1fbb06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:04 +0200 Subject: [PATCH 18/20] New translations base.json (Danish) --- EmbyStat.Web/ClientApp/public/locales/da-DK.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/da-DK.json b/EmbyStat.Web/ClientApp/public/locales/da-DK.json index 7d68773e1..25c8f1e00 100644 --- a/EmbyStat.Web/ClientApp/public/locales/da-DK.json +++ b/EmbyStat.Web/ClientApp/public/locales/da-DK.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Update level", "VERSION": "Version", "VIDEORANGE": "Video range", - "WIDTH": "Width" + "WIDTH": "Width", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Senest aktiv", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Brugere", "WATCHED": { From 2e3498e639ffd0923bdf14554d7b0afe7c1870be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:05 +0200 Subject: [PATCH 19/20] New translations base.json (Spanish) --- EmbyStat.Web/ClientApp/public/locales/es-ES.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/es-ES.json b/EmbyStat.Web/ClientApp/public/locales/es-ES.json index 36abe1c15..38f006ad6 100644 --- a/EmbyStat.Web/ClientApp/public/locales/es-ES.json +++ b/EmbyStat.Web/ClientApp/public/locales/es-ES.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "Nivel de actualización", "VERSION": "Versión", "VIDEORANGE": "Rango de video", - "WIDTH": "Anchura" + "WIDTH": "Anchura", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Última conexión", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Usuarios", "WATCHED": { From 73ebf51399b64dbb2d66c3021422244534de2b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikha=C3=ABl=20Regni?= Date: Thu, 14 Jul 2022 15:18:06 +0200 Subject: [PATCH 20/20] New translations base.json (Portuguese, Brazilian) --- EmbyStat.Web/ClientApp/public/locales/pt-BR.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/EmbyStat.Web/ClientApp/public/locales/pt-BR.json b/EmbyStat.Web/ClientApp/public/locales/pt-BR.json index 70ab105de..fca8feb82 100644 --- a/EmbyStat.Web/ClientApp/public/locales/pt-BR.json +++ b/EmbyStat.Web/ClientApp/public/locales/pt-BR.json @@ -89,7 +89,8 @@ "UPDATELEVEL": "NÍVEL DE ATUALIZAÇÃO", "VERSION": "Versão", "VIDEORANGE": "Intervalo de vídeo", - "WIDTH": "Largura" + "WIDTH": "Largura", + "NEVER": "Never" }, "DIALOGS": { "EDIT_TRIGGER": { @@ -344,6 +345,7 @@ "HIDDEN": "Hidden", "IDLEUSERS": "Idle users", "LASTACTIVE": "Ativo pela última vez", + "LASTLOGIN": "Last login", "LOADER": "Loading user info", "USERS": "Usuários", "WATCHED": {