diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index eca16ad38..5e3455fba 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -15,6 +15,7 @@ - [cvium](https://github.com/cvium) - [wtayl0r](https://github.com/wtayl0r) - [TtheCreator](https://github.com/Tthecreator) + - [dkanada](https://github.com/dkanada) - [LogicalPhallacy](https://github.com/LogicalPhallacy/) - [RazeLighter777](https://github.com/RazeLighter777) diff --git a/Dockerfile b/Dockerfile index c79d6f8ee..266b59f1c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \ --output /jellyfin \ Jellyfin.Server -FROM jrottenberg/ffmpeg:4.0-scratch as ffmpeg +FROM jrottenberg/ffmpeg:4.0-vaapi as ffmpeg FROM microsoft/dotnet:${DOTNET_VERSION}-runtime # libfontconfig1 is required for Skia RUN apt-get update \ @@ -23,4 +23,4 @@ COPY --from=ffmpeg / / COPY --from=builder /jellyfin /jellyfin EXPOSE 8096 VOLUME /config /media -ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config +ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config diff --git a/Dockerfile.arm b/Dockerfile.arm index 657cb4ac6..039274197 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -21,4 +21,4 @@ RUN apt-get update \ COPY --from=builder /jellyfin /jellyfin EXPOSE 8096 VOLUME /config /media -ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config +ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 979dfc1dc..06ba21b91 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -30,4 +30,4 @@ COPY --from=qemu_extract qemu-* /usr/bin COPY --from=builder /jellyfin /jellyfin EXPOSE 8096 VOLUME /config /media -ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config +ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 01c9fe50f..68bf80163 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -236,7 +236,9 @@ namespace Emby.Dlna.Api public object Get(GetIcon request) { - var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower(); + var contentType = "image/" + Path.GetExtension(request.Filename) + .TrimStart('.') + .ToLowerInvariant(); var cacheLength = TimeSpan.FromDays(365); var cacheKey = Request.RawUrl.GetMD5(); diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 2d8bb87f9..1150afdba 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -63,7 +63,7 @@ namespace Emby.Dlna.ContentDirectory _profile = profile; _config = config; - _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, libraryManager, mediaEncoder); + _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder); } protected override IEnumerable> GetResult(string methodName, IDictionary methodParams) @@ -454,7 +454,7 @@ namespace Emby.Dlna.ContentDirectory User = user, Recursive = true, IsMissing = false, - ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name }, + ExcludeItemTypes = new[] { typeof(Book).Name }, IsFolder = isFolder, MediaTypes = mediaTypes.ToArray(), DtoOptions = GetDtoOptions() @@ -523,7 +523,7 @@ namespace Emby.Dlna.ContentDirectory Limit = limit, StartIndex = startIndex, IsVirtualItem = false, - ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name }, + ExcludeItemTypes = new[] { typeof(Book).Name }, IsPlaceHolder = false, DtoOptions = GetDtoOptions() }; diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index e4a9cbfc6..605f4f37b 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -43,22 +43,30 @@ namespace Emby.Dlna.Didl private readonly ILocalizationManager _localization; private readonly IMediaSourceManager _mediaSourceManager; private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; private readonly IMediaEncoder _mediaEncoder; - public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder) + public DidlBuilder( + DeviceProfile profile, + User user, + IImageProcessor imageProcessor, + string serverAddress, + string accessToken, + IUserDataManager userDataManager, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + ILogger logger, + IMediaEncoder mediaEncoder) { _profile = profile; + _user = user; _imageProcessor = imageProcessor; _serverAddress = serverAddress; + _accessToken = accessToken; _userDataManager = userDataManager; _localization = localization; _mediaSourceManager = mediaSourceManager; _logger = logger; - _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; - _accessToken = accessToken; - _user = user; } public static string NormalizeDlnaMediaUrl(string url) @@ -117,7 +125,8 @@ namespace Emby.Dlna.Didl } } - public void WriteItemElement(DlnaOptions options, + public void WriteItemElement( + DlnaOptions options, XmlWriter writer, BaseItem item, User user, @@ -232,12 +241,15 @@ namespace Emby.Dlna.Didl AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo); } - var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken) - .Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External) - .ToList(); + var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken); foreach (var subtitle in subtitleProfiles) { + if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External) + { + continue; + } + var subtitleAdded = AddSubtitleElement(writer, subtitle); if (subtitleAdded && _profile.EnableSingleSubtitleLimit) @@ -250,7 +262,8 @@ namespace Emby.Dlna.Didl private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info) { var subtitleProfile = _profile.SubtitleProfiles - .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External); + .FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) + && i.Method == SubtitleDeliveryMethod.External); if (subtitleProfile == null) { @@ -265,7 +278,7 @@ namespace Emby.Dlna.Didl // http://192.168.1.3:9999/video.srt writer.WriteStartElement("sec", "CaptionInfoEx", null); - writer.WriteAttributeString("sec", "type", null, info.Format.ToLower()); + writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant()); writer.WriteString(info.Url); writer.WriteFullEndElement(); @@ -282,7 +295,7 @@ namespace Emby.Dlna.Didl else { writer.WriteStartElement(string.Empty, "res", NS_DIDL); - var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower()); + var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant()); writer.WriteAttributeString("protocolInfo", protocolInfo); writer.WriteString(info.Url); @@ -387,91 +400,39 @@ namespace Emby.Dlna.Didl private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context) { - if (itemStubType.HasValue && itemStubType.Value == StubType.Latest) + if (itemStubType.HasValue) { - return _localization.GetLocalizedString("Latest"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists) - { - return _localization.GetLocalizedString("Playlists"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists) - { - return _localization.GetLocalizedString("HeaderAlbumArtists"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Albums) - { - return _localization.GetLocalizedString("Albums"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Artists) - { - return _localization.GetLocalizedString("Artists"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Songs) - { - return _localization.GetLocalizedString("Songs"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Genres) - { - return _localization.GetLocalizedString("Genres"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteAlbums) - { - return _localization.GetLocalizedString("HeaderFavoriteAlbums"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteArtists) - { - return _localization.GetLocalizedString("HeaderFavoriteArtists"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSongs) - { - return _localization.GetLocalizedString("HeaderFavoriteSongs"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.ContinueWatching) - { - return _localization.GetLocalizedString("HeaderContinueWatching"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Movies) - { - return _localization.GetLocalizedString("Movies"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Collections) - { - return _localization.GetLocalizedString("Collections"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Favorites) - { - return _localization.GetLocalizedString("Favorites"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.NextUp) - { - return _localization.GetLocalizedString("HeaderNextUp"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSeries) - { - return _localization.GetLocalizedString("HeaderFavoriteShows"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteEpisodes) - { - return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); - } - if (itemStubType.HasValue && itemStubType.Value == StubType.Series) - { - return _localization.GetLocalizedString("Shows"); + switch (itemStubType.Value) + { + case StubType.Latest: return _localization.GetLocalizedString("Latest"); + case StubType.Playlists: return _localization.GetLocalizedString("Playlists"); + case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists"); + case StubType.Albums: return _localization.GetLocalizedString("Albums"); + case StubType.Artists: return _localization.GetLocalizedString("Artists"); + case StubType.Songs: return _localization.GetLocalizedString("Songs"); + case StubType.Genres: return _localization.GetLocalizedString("Genres"); + case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums"); + case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists"); + case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs"); + case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching"); + case StubType.Movies: return _localization.GetLocalizedString("Movies"); + case StubType.Collections: return _localization.GetLocalizedString("Collections"); + case StubType.Favorites: return _localization.GetLocalizedString("Favorites"); + case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp"); + case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows"); + case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes"); + case StubType.Series: return _localization.GetLocalizedString("Shows"); + default: break; + } } - var episode = item as Episode; - var season = context as Season; - - if (episode != null && season != null) + if (item is Episode episode && context is Season season) { // This is a special embedded within a season - if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0) + if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0 + && season.IndexNumber.HasValue && season.IndexNumber.Value != 0) { - if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0) - { - return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name); - } + return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name); } if (item.IndexNumber.HasValue) @@ -585,10 +546,8 @@ namespace Emby.Dlna.Didl public static bool IsIdRoot(string id) { - if (string.IsNullOrWhiteSpace(id) || - - string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) - + if (string.IsNullOrWhiteSpace(id) + || string.Equals(id, "0", StringComparison.OrdinalIgnoreCase) // Samsung sometimes uses 1 as root || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase)) { @@ -808,7 +767,7 @@ namespace Emby.Dlna.Didl { writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre"); } - else if (item is Genre || item is GameGenre) + else if (item is Genre) { writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre"); } @@ -844,7 +803,7 @@ namespace Emby.Dlna.Didl // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) // ?? PersonType.Actor; - // AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP); + // AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP); // index++; @@ -1112,7 +1071,7 @@ namespace Emby.Dlna.Didl }; } - class ImageDownloadInfo + private class ImageDownloadInfo { internal Guid ItemId; internal string ImageTag; @@ -1128,7 +1087,7 @@ namespace Emby.Dlna.Didl internal ItemImageInfo ItemImageInfo; } - class ImageUrlInfo + private class ImageUrlInfo { internal string Url; @@ -1147,7 +1106,7 @@ namespace Emby.Dlna.Didl if (stubType.HasValue) { - id = stubType.Value.ToString().ToLower() + "_" + id; + id = stubType.Value.ToString().ToLowerInvariant() + "_" + id; } return id; diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index fd7b408fd..c507b14e9 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Emby.Dlna.Profiles; using Emby.Dlna.Server; using MediaBrowser.Common.Configuration; @@ -48,11 +49,11 @@ namespace Emby.Dlna _assemblyInfo = assemblyInfo; } - public void InitProfiles() + public async Task InitProfilesAsync() { try { - ExtractSystemProfiles(); + await ExtractSystemProfilesAsync(); LoadProfiles(); } catch (Exception ex) @@ -300,7 +301,7 @@ namespace Emby.Dlna profile = ReserializeProfile(tempProfile); - profile.Id = path.ToLower().GetMD5().ToString("N"); + profile.Id = path.ToLowerInvariant().GetMD5().ToString("N"); _profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile); @@ -352,14 +353,14 @@ namespace Emby.Dlna Info = new DeviceProfileInfo { - Id = file.FullName.ToLower().GetMD5().ToString("N"), + Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"), Name = _fileSystem.GetFileNameWithoutExtension(file), Type = type } }; } - private void ExtractSystemProfiles() + private async Task ExtractSystemProfilesAsync() { var namespaceName = GetType().Namespace + ".Profiles.Xml."; @@ -383,7 +384,7 @@ namespace Emby.Dlna using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { - stream.CopyTo(fileStream); + await stream.CopyToAsync(fileStream); } } } @@ -506,7 +507,7 @@ namespace Emby.Dlna ? ImageFormat.Png : ImageFormat.Jpg; - var resource = GetType().Namespace + ".Images." + filename.ToLower(); + var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant(); return new ImageStream { diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 1ab6014eb..a20006578 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -20,7 +20,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; -using MediaBrowser.Model.Threading; using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; using Rssdp; @@ -49,8 +48,7 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; private SsdpDevicePublisher _Publisher; - - private readonly ITimerFactory _timerFactory; + private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; private readonly INetworkManager _networkManager; @@ -78,7 +76,6 @@ namespace Emby.Dlna.Main IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, - ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, INetworkManager networkManager, IUserViewManager userViewManager, @@ -99,7 +96,6 @@ namespace Emby.Dlna.Main _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; - _timerFactory = timerFactory; _environmentInfo = environmentInfo; _networkManager = networkManager; _logger = loggerFactory.CreateLogger("Dlna"); @@ -125,9 +121,9 @@ namespace Emby.Dlna.Main Current = this; } - public void Run() + public async Task RunAsync() { - ((DlnaManager)_dlnaManager).InitProfiles(); + await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); ReloadComponents(); @@ -233,7 +229,7 @@ namespace Emby.Dlna.Main try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); _Publisher.LogFunction = LogMessage; _Publisher.SupportPnpRootDevice = false; @@ -263,7 +259,7 @@ namespace Emby.Dlna.Main var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString()); + _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); var descriptorUri = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); @@ -353,8 +349,7 @@ namespace Emby.Dlna.Main _userDataManager, _localization, _mediaSourceManager, - _mediaEncoder, - _timerFactory); + _mediaEncoder); _manager.Start(); } diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 68aa0a6a7..037cdd8aa 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -10,7 +10,6 @@ using Emby.Dlna.Server; using Emby.Dlna.Ssdp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Dlna.PlayTo @@ -19,7 +18,7 @@ namespace Emby.Dlna.PlayTo { #region Fields & Properties - private ITimer _timer; + private Timer _timer; public DeviceInfo Properties { get; set; } @@ -40,12 +39,7 @@ namespace Emby.Dlna.PlayTo public TimeSpan? Duration { get; set; } - private TimeSpan _position = TimeSpan.FromSeconds(0); - public TimeSpan Position - { - get => _position; - set => _position = value; - } + public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0); public TRANSPORTSTATE TransportState { get; private set; } @@ -61,24 +55,20 @@ namespace Emby.Dlna.PlayTo private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - public DateTime DateLastActivity { get; private set; } public Action OnDeviceUnavailable { get; set; } - private readonly ITimerFactory _timerFactory; - - public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory) + public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config) { Properties = deviceProperties; _httpClient = httpClient; _logger = logger; _config = config; - _timerFactory = timerFactory; } public void Start() { _logger.LogDebug("Dlna Device.Start"); - _timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite); + _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite); } private DateTime _lastVolumeRefresh; @@ -119,7 +109,9 @@ namespace Emby.Dlna.PlayTo lock (_timerLock) { if (_disposed) + { return; + } _volumeRefreshActive = true; @@ -136,7 +128,9 @@ namespace Emby.Dlna.PlayTo lock (_timerLock) { if (_disposed) + { return; + } _volumeRefreshActive = false; @@ -144,11 +138,6 @@ namespace Emby.Dlna.PlayTo } } - public void OnPlaybackStartedExternally() - { - RestartTimer(true); - } - #region Commanding public Task VolumeDown(CancellationToken cancellationToken) @@ -333,7 +322,9 @@ namespace Emby.Dlna.PlayTo private string CreateDidlMeta(string value) { if (string.IsNullOrEmpty(value)) + { return string.Empty; + } return DescriptionXmlBuilder.Escape(value); } @@ -342,10 +333,11 @@ namespace Emby.Dlna.PlayTo { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play"); if (command == null) + { return Task.CompletedTask; + } var service = GetAvTransportService(); - if (service == null) { throw new InvalidOperationException("Unable to find service"); @@ -369,7 +361,9 @@ namespace Emby.Dlna.PlayTo var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop"); if (command == null) + { return; + } var service = GetAvTransportService(); @@ -385,7 +379,9 @@ namespace Emby.Dlna.PlayTo var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause"); if (command == null) + { return; + } var service = GetAvTransportService(); @@ -405,7 +401,9 @@ namespace Emby.Dlna.PlayTo private async void TimerCallback(object sender) { if (_disposed) + { return; + } try { @@ -425,8 +423,6 @@ namespace Emby.Dlna.PlayTo return; } - DateLastActivity = DateTime.UtcNow; - if (transportState.HasValue) { // If we're not playing anything no need to get additional data @@ -505,7 +501,9 @@ namespace Emby.Dlna.PlayTo var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume"); if (command == null) + { return; + } var service = GetServiceRenderingControl(); @@ -518,13 +516,17 @@ namespace Emby.Dlna.PlayTo .ConfigureAwait(false); if (result == null || result.Document == null) + { return; + } var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null); - var volumeValue = volume == null ? null : volume.Value; + var volumeValue = volume?.Value; if (string.IsNullOrWhiteSpace(volumeValue)) + { return; + } Volume = int.Parse(volumeValue, UsCulture); @@ -545,7 +547,9 @@ namespace Emby.Dlna.PlayTo var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute"); if (command == null) + { return; + } var service = GetServiceRenderingControl(); @@ -560,39 +564,44 @@ namespace Emby.Dlna.PlayTo if (result == null || result.Document == null) return; - var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null); - var value = valueNode == null ? null : valueNode.Value; + var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse") + .Select(i => i.Element("CurrentMute")) + .FirstOrDefault(i => i != null); - IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase); + IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase); } private async Task GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo"); if (command == null) + { return null; + } var service = GetAvTransportService(); if (service == null) + { return null; + } var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false) .ConfigureAwait(false); if (result == null || result.Document == null) + { return null; + } var transportState = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null); var transportStateValue = transportState == null ? null : transportState.Value; - if (transportStateValue != null) + if (transportStateValue != null + && Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) { - if (Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state)) - { - return state; - } + return state; } return null; @@ -602,10 +611,11 @@ namespace Emby.Dlna.PlayTo { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo"); if (command == null) + { return null; + } var service = GetAvTransportService(); - if (service == null) { throw new InvalidOperationException("Unable to find service"); @@ -617,7 +627,9 @@ namespace Emby.Dlna.PlayTo .ConfigureAwait(false); if (result == null || result.Document == null) + { return null; + } var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault(); @@ -657,11 +669,13 @@ namespace Emby.Dlna.PlayTo return null; } - private async Task> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) + private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken) { var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo"); if (command == null) - return new Tuple(false, null); + { + return (false, null); + } var service = GetAvTransportService(); @@ -676,7 +690,9 @@ namespace Emby.Dlna.PlayTo .ConfigureAwait(false); if (result == null || result.Document == null) - return new Tuple(false, null); + { + return (false, null); + } var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null); var trackUri = trackUriElem == null ? null : trackUriElem.Value; @@ -684,8 +700,8 @@ namespace Emby.Dlna.PlayTo var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null); var duration = durationElem == null ? null : durationElem.Value; - if (!string.IsNullOrWhiteSpace(duration) && - !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(duration) + && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { Duration = TimeSpan.Parse(duration, UsCulture); } @@ -707,14 +723,14 @@ namespace Emby.Dlna.PlayTo if (track == null) { //If track is null, some vendors do this, use GetMediaInfo instead - return new Tuple(true, null); + return (true, null); } var trackString = (string)track; if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - return new Tuple(true, null); + return (true, null); } XElement uPnpResponse; @@ -735,7 +751,7 @@ namespace Emby.Dlna.PlayTo catch (Exception ex) { _logger.LogError(ex, "Unable to parse xml {0}", trackString); - return new Tuple(true, null); + return (true, null); } } @@ -743,7 +759,7 @@ namespace Emby.Dlna.PlayTo var uTrack = CreateUBaseObject(e, trackUri); - return new Tuple(true, uTrack); + return (true, uTrack); } private static uBaseObject CreateUBaseObject(XElement container, string trackUri) @@ -801,11 +817,9 @@ namespace Emby.Dlna.PlayTo private async Task GetAVProtocolAsync(CancellationToken cancellationToken) { - var avCommands = AvCommands; - - if (avCommands != null) + if (AvCommands != null) { - return avCommands; + return AvCommands; } if (_disposed) @@ -825,18 +839,15 @@ namespace Emby.Dlna.PlayTo var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - avCommands = TransportCommands.Create(document); - AvCommands = avCommands; - return avCommands; + AvCommands = TransportCommands.Create(document); + return AvCommands; } private async Task GetRenderingProtocolAsync(CancellationToken cancellationToken) { - var rendererCommands = RendererCommands; - - if (rendererCommands != null) + if (RendererCommands != null) { - return rendererCommands; + return RendererCommands; } if (_disposed) @@ -845,7 +856,6 @@ namespace Emby.Dlna.PlayTo } var avService = GetServiceRenderingControl(); - if (avService == null) { throw new ArgumentException("Device AvService is null"); @@ -857,9 +867,8 @@ namespace Emby.Dlna.PlayTo _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync"); var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false); - rendererCommands = TransportCommands.Create(document); - RendererCommands = rendererCommands; - return rendererCommands; + RendererCommands = TransportCommands.Create(document); + return RendererCommands; } private string NormalizeUrl(string baseUrl, string url) @@ -871,85 +880,103 @@ namespace Emby.Dlna.PlayTo } if (!url.Contains("/")) + { url = "/dmr/" + url; + } + if (!url.StartsWith("/")) + { url = "/" + url; + } return baseUrl + url; } - private TransportCommands AvCommands - { - get; - set; - } + private TransportCommands AvCommands { get; set; } - private TransportCommands RendererCommands - { - get; - set; - } + private TransportCommands RendererCommands { get; set; } - public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory, CancellationToken cancellationToken) + public static async Task CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken) { var ssdpHttpClient = new SsdpHttpClient(httpClient, config); var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false); - var deviceProperties = new DeviceInfo(); - var friendlyNames = new List(); var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault(); if (name != null && !string.IsNullOrWhiteSpace(name.Value)) + { friendlyNames.Add(name.Value); + } var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault(); if (room != null && !string.IsNullOrWhiteSpace(room.Value)) + { friendlyNames.Add(room.Value); + } - deviceProperties.Name = string.Join(" ", friendlyNames.ToArray()); + var deviceProperties = new DeviceInfo() + { + Name = string.Join(" ", friendlyNames), + BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port) + }; var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault(); if (model != null) + { deviceProperties.ModelName = model.Value; + } var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault(); if (modelNumber != null) + { deviceProperties.ModelNumber = modelNumber.Value; + } var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault(); if (uuid != null) + { deviceProperties.UUID = uuid.Value; + } var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault(); if (manufacturer != null) + { deviceProperties.Manufacturer = manufacturer.Value; + } var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault(); if (manufacturerUrl != null) + { deviceProperties.ManufacturerUrl = manufacturerUrl.Value; + } var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault(); if (presentationUrl != null) + { deviceProperties.PresentationUrl = presentationUrl.Value; + } var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault(); if (modelUrl != null) + { deviceProperties.ModelUrl = modelUrl.Value; + } var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault(); if (serialNumber != null) + { deviceProperties.SerialNumber = serialNumber.Value; + } var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault(); if (modelDescription != null) + { deviceProperties.ModelDescription = modelDescription.Value; - - deviceProperties.BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port); + } var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault(); - if (icon != null) { deviceProperties.Icon = CreateIcon(icon); @@ -958,12 +985,15 @@ namespace Emby.Dlna.PlayTo foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList"))) { if (services == null) + { continue; + } var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service")); - if (servicesList == null) + { continue; + } foreach (var element in servicesList) { @@ -976,9 +1006,7 @@ namespace Emby.Dlna.PlayTo } } - var device = new Device(deviceProperties, httpClient, logger, config, timerFactory); - - return device; + return new Device(deviceProperties, httpClient, logger, config); } #endregion @@ -1065,13 +1093,10 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackStart(uBaseObject mediaInfo) { - if (PlaybackStart != null) + PlaybackStart?.Invoke(this, new PlaybackStartEventArgs { - PlaybackStart.Invoke(this, new PlaybackStartEventArgs - { - MediaInfo = mediaInfo - }); - } + MediaInfo = mediaInfo + }); } private void OnPlaybackProgress(uBaseObject mediaInfo) @@ -1082,58 +1107,56 @@ namespace Emby.Dlna.PlayTo return; } - if (PlaybackProgress != null) + PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs { - PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs - { - MediaInfo = mediaInfo - }); - } + MediaInfo = mediaInfo + }); } private void OnPlaybackStop(uBaseObject mediaInfo) { - if (PlaybackStopped != null) + + PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs { - PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs - { - MediaInfo = mediaInfo - }); - } + MediaInfo = mediaInfo + }); } private void OnMediaChanged(uBaseObject old, uBaseObject newMedia) { - if (MediaChanged != null) + MediaChanged?.Invoke(this, new MediaChangedEventArgs { - MediaChanged.Invoke(this, new MediaChangedEventArgs - { - OldMediaInfo = old, - NewMediaInfo = newMedia - }); - } + OldMediaInfo = old, + NewMediaInfo = newMedia + }); } #region IDisposable bool _disposed; + public void Dispose() { - if (!_disposed) - { - _disposed = true; - - DisposeTimer(); - } + Dispose(true); + GC.SuppressFinalize(this); } - private void DisposeTimer() + protected virtual void Dispose(bool disposing) { - if (_timer != null) + if (_disposed) { - _timer.Dispose(); - _timer = null; + return; } + + if (disposing) + { + _timer?.Dispose(); + } + + _timer = null; + Properties = null; + + _disposed = true; } #endregion diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 409b8442c..be86dde16 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -42,30 +42,43 @@ namespace Emby.Dlna.PlayTo private readonly IDeviceDiscovery _deviceDiscovery; private readonly string _serverAddress; private readonly string _accessToken; - private readonly DateTime _creationTime; public bool IsSessionActive => !_disposed && _device != null; public bool SupportsMediaControl => IsSessionActive; - public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) + public PlayToController( + SessionInfo session, + ISessionManager sessionManager, + ILibraryManager libraryManager, + ILogger logger, + IDlnaManager dlnaManager, + IUserManager userManager, + IImageProcessor imageProcessor, + string serverAddress, + string accessToken, + IDeviceDiscovery deviceDiscovery, + IUserDataManager userDataManager, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IConfigurationManager config, + IMediaEncoder mediaEncoder) { _session = session; _sessionManager = sessionManager; _libraryManager = libraryManager; + _logger = logger; _dlnaManager = dlnaManager; _userManager = userManager; _imageProcessor = imageProcessor; _serverAddress = serverAddress; + _accessToken = accessToken; _deviceDiscovery = deviceDiscovery; _userDataManager = userDataManager; _localization = localization; _mediaSourceManager = mediaSourceManager; _config = config; _mediaEncoder = mediaEncoder; - _accessToken = accessToken; - _logger = logger; - _creationTime = DateTime.UtcNow; } public void Init(Device device) @@ -374,9 +387,7 @@ namespace Emby.Dlna.PlayTo return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None); case PlaystateCommand.Seek: - { - return Seek(command.SeekPositionTicks ?? 0); - } + return Seek(command.SeekPositionTicks ?? 0); case PlaystateCommand.NextTrack: return SetPlaylistIndex(_currentPlaylistIndex + 1); @@ -442,8 +453,7 @@ namespace Emby.Dlna.PlayTo var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ?? _dlnaManager.GetDefaultProfile(); - var hasMediaSources = item as IHasMediaSources; - var mediaSources = hasMediaSources != null + var mediaSources = item is IHasMediaSources ? (_mediaSourceManager.GetStaticMediaSources(item, true, user)) : new List(); @@ -452,7 +462,7 @@ namespace Emby.Dlna.PlayTo playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken)); - var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder) + var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder) .GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo); playlistItem.Didl = itemXml; diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs index 790625705..6cce312ee 100644 --- a/Emby.Dlna/PlayTo/PlayToManager.cs +++ b/Emby.Dlna/PlayTo/PlayToManager.cs @@ -16,7 +16,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Dlna.PlayTo @@ -39,13 +38,12 @@ namespace Emby.Dlna.PlayTo private readonly IDeviceDiscovery _deviceDiscovery; private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; - private readonly ITimerFactory _timerFactory; private bool _disposed; private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory) + public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _logger = logger; _sessionManager = sessionManager; @@ -61,7 +59,6 @@ namespace Emby.Dlna.PlayTo _localization = localization; _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; - _timerFactory = timerFactory; } public void Start() @@ -168,7 +165,7 @@ namespace Emby.Dlna.PlayTo if (controller == null) { - var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false); + var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false); string deviceName = device.Properties.Name; diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index e0ecbee43..03d8f80ab 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -107,19 +107,19 @@ namespace Emby.Dlna.Server '&' }; - private static readonly string[] s_escapeStringPairs = new string[] -{ - "<", - "<", - ">", - ">", - "\"", - """, - "'", - "'", - "&", - "&" -}; + private static readonly string[] s_escapeStringPairs = new[] + { + "<", + "<", + ">", + ">", + "\"", + """, + "'", + "'", + "&", + "&" + }; private static string GetEscapeSequence(char c) { @@ -133,7 +133,7 @@ namespace Emby.Dlna.Server return result; } } - return c.ToString(); + return c.ToString(CultureInfo.InvariantCulture); } /// Replaces invalid XML characters in a string with their valid XML equivalent. @@ -145,6 +145,7 @@ namespace Emby.Dlna.Server { return null; } + StringBuilder stringBuilder = null; int length = str.Length; int num = 0; @@ -230,9 +231,9 @@ namespace Emby.Dlna.Server var serverName = new string(characters); - var name = (_profile.FriendlyName ?? string.Empty).Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase); + var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase); - return name; + return name ?? string.Empty; } private void AppendIconList(StringBuilder builder) @@ -295,65 +296,62 @@ namespace Emby.Dlna.Server } private IEnumerable GetIcons() - { - var list = new List(); - - list.Add(new DeviceIcon + => new[] { - MimeType = "image/png", - Depth = "24", - Width = 240, - Height = 240, - Url = "icons/logo240.png" - }); + new DeviceIcon + { + MimeType = "image/png", + Depth = "24", + Width = 240, + Height = 240, + Url = "icons/logo240.png" + }, - list.Add(new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 240, - Height = 240, - Url = "icons/logo240.jpg" - }); + new DeviceIcon + { + MimeType = "image/jpeg", + Depth = "24", + Width = 240, + Height = 240, + Url = "icons/logo240.jpg" + }, - list.Add(new DeviceIcon - { - MimeType = "image/png", - Depth = "24", - Width = 120, - Height = 120, - Url = "icons/logo120.png" - }); + new DeviceIcon + { + MimeType = "image/png", + Depth = "24", + Width = 120, + Height = 120, + Url = "icons/logo120.png" + }, - list.Add(new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 120, - Height = 120, - Url = "icons/logo120.jpg" - }); + new DeviceIcon + { + MimeType = "image/jpeg", + Depth = "24", + Width = 120, + Height = 120, + Url = "icons/logo120.jpg" + }, - list.Add(new DeviceIcon - { - MimeType = "image/png", - Depth = "24", - Width = 48, - Height = 48, - Url = "icons/logo48.png" - }); + new DeviceIcon + { + MimeType = "image/png", + Depth = "24", + Width = 48, + Height = 48, + Url = "icons/logo48.png" + }, - list.Add(new DeviceIcon - { - MimeType = "image/jpeg", - Depth = "24", - Width = 48, - Height = 48, - Url = "icons/logo48.jpg" - }); - - return list; - } + new DeviceIcon + { + MimeType = "image/jpeg", + Depth = "24", + Width = 48, + Height = 48, + Url = "icons/logo48.jpg" + } + }; private IEnumerable GetServices() { diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs index 89a88705f..298f68a28 100644 --- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs +++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; @@ -48,20 +47,17 @@ namespace Emby.Dlna.Ssdp private SsdpDeviceLocator _deviceLocator; - private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; private ISsdpCommunicationsServer _commsServer; public DeviceDiscovery( ILoggerFactory loggerFactory, IServerConfigurationManager config, - ISocketFactory socketFactory, - ITimerFactory timerFactory) + ISocketFactory socketFactory) { _logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery)); _config = config; _socketFactory = socketFactory; - _timerFactory = timerFactory; } // Call this method from somewhere in your code to start the search. @@ -78,7 +74,7 @@ namespace Emby.Dlna.Ssdp { if (_listenerCount > 0 && _deviceLocator == null) { - _deviceLocator = new SsdpDeviceLocator(_commsServer, _timerFactory); + _deviceLocator = new SsdpDeviceLocator(_commsServer); // (Optional) Set the filter so we only see notifications for devices we care about // (can be any search target value i.e device type, uuid value etc - any value that appears in the diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 4901561eb..faaeb5af8 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -83,8 +83,8 @@ namespace Emby.Drawing } } - public string[] SupportedInputFormats => - new string[] + public IReadOnlyCollection SupportedInputFormats => + new HashSet(StringComparer.OrdinalIgnoreCase) { "tiff", "tif", @@ -137,14 +137,14 @@ namespace Emby.Drawing } } - public ImageFormat[] GetSupportedImageOutputFormats() - { - return _imageEncoder.SupportedOutputFormats; - } + public IReadOnlyCollection GetSupportedImageOutputFormats() + => _imageEncoder.SupportedOutputFormats; + + private static readonly HashSet TransparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".webp", ".gif" }; - private static readonly string[] TransparentImageTypes = new string[] { ".png", ".webp", ".gif" }; public bool SupportsTransparency(string path) - => TransparentImageTypes.Contains(Path.GetExtension(path).ToLower()); + => TransparentImageTypes.Contains(Path.GetExtension(path)); public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options) { @@ -261,15 +261,6 @@ namespace Emby.Drawing return (cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } - catch (ArgumentOutOfRangeException ex) - { - // Decoder failed to decode it -#if DEBUG - _logger.LogError(ex, "Error encoding image"); -#endif - // Just spit out the original file if all the options are default - return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); - } catch (Exception ex) { // If it fails for whatever reason, return the original image @@ -374,13 +365,13 @@ namespace Emby.Drawing filename += "v=" + Version; - return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower()); + return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant()); } - public ImageDimensions GetImageSize(BaseItem item, ItemImageInfo info) - => GetImageSize(item, info, true); + public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info) + => GetImageDimensions(item, info, true); - public ImageDimensions GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem) + public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem) { int width = info.Width; int height = info.Height; @@ -393,7 +384,7 @@ namespace Emby.Drawing string path = info.Path; _logger.LogInformation("Getting image size for item {ItemType} {Path}", item.GetType().Name, path); - ImageDimensions size = GetImageSize(path); + ImageDimensions size = GetImageDimensions(path); info.Width = size.Width; info.Height = size.Height; @@ -408,7 +399,7 @@ namespace Emby.Drawing /// /// Gets the size of the image. /// - public ImageDimensions GetImageSize(string path) + public ImageDimensions GetImageDimensions(string path) => _imageEncoder.GetImageSize(path); /// @@ -481,7 +472,7 @@ namespace Emby.Drawing return (originalImagePath, dateModified); } - if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat, StringComparer.OrdinalIgnoreCase)) + if (!_imageEncoder.SupportedInputFormats.Contains(inputFormat)) { try { diff --git a/Emby.Drawing/NullImageEncoder.cs b/Emby.Drawing/NullImageEncoder.cs index 14f0424ac..fc4a5af9f 100644 --- a/Emby.Drawing/NullImageEncoder.cs +++ b/Emby.Drawing/NullImageEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Drawing; @@ -6,15 +7,11 @@ namespace Emby.Drawing { public class NullImageEncoder : IImageEncoder { - public string[] SupportedInputFormats => - new[] - { - "png", - "jpeg", - "jpg" - }; + public IReadOnlyCollection SupportedInputFormats + => new HashSet(StringComparer.OrdinalIgnoreCase) { "png", "jpeg", "jpg" }; - public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Jpg, ImageFormat.Png }; + public IReadOnlyCollection SupportedOutputFormats + => new HashSet() { ImageFormat.Jpg, ImageFormat.Png }; public void CropWhiteSpace(string inputPath, string outputPath) { diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs index c5564fd61..a6fc53953 100644 --- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs +++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs @@ -121,7 +121,7 @@ namespace IsoMounter path, Path.GetExtension(path), EnvironmentInfo.OperatingSystem, - ExecutablesAvailable.ToString() + ExecutablesAvailable ); if (ExecutablesAvailable) @@ -183,7 +183,7 @@ namespace IsoMounter _logger.LogInformation( "[{0}] Disposing [{1}].", Name, - disposing.ToString() + disposing ); if (disposing) @@ -229,9 +229,8 @@ namespace IsoMounter var uid = getuid(); _logger.LogDebug( - "[{0}] Our current UID is [{1}], GetUserId() returned [{2}].", + "[{0}] GetUserId() returned [{2}].", Name, - uid.ToString(), uid ); diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs index 158898084..8cc14fa01 100644 --- a/Emby.Notifications/CoreNotificationTypes.cs +++ b/Emby.Notifications/CoreNotificationTypes.cs @@ -73,11 +73,6 @@ namespace Emby.Notifications Type = NotificationType.AudioPlayback.ToString() }, - new NotificationTypeInfo - { - Type = NotificationType.GamePlayback.ToString() - }, - new NotificationTypeInfo { Type = NotificationType.VideoPlayback.ToString() @@ -88,11 +83,6 @@ namespace Emby.Notifications Type = NotificationType.AudioPlaybackStopped.ToString() }, - new NotificationTypeInfo - { - Type = NotificationType.GamePlaybackStopped.ToString() - }, - new NotificationTypeInfo { Type = NotificationType.VideoPlaybackStopped.ToString() diff --git a/Emby.Notifications/Notifications.cs b/Emby.Notifications/Notifications.cs index fbdc39f94..d3290479f 100644 --- a/Emby.Notifications/Notifications.cs +++ b/Emby.Notifications/Notifications.cs @@ -20,7 +20,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Notifications; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Notifications @@ -40,9 +39,8 @@ namespace Emby.Notifications private readonly ILibraryManager _libraryManager; private readonly ISessionManager _sessionManager; private readonly IServerApplicationHost _appHost; - private readonly ITimerFactory _timerFactory; - private ITimer LibraryUpdateTimer { get; set; } + private Timer LibraryUpdateTimer { get; set; } private readonly object _libraryChangedSyncLock = new object(); private readonly IConfigurationManager _config; @@ -52,7 +50,7 @@ namespace Emby.Notifications private string[] _coreNotificationTypes; - public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager, ITimerFactory timerFactory) + public Notifications(IInstallationManager installationManager, IActivityManager activityManager, ILocalizationManager localization, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager) { _installationManager = installationManager; _userManager = userManager; @@ -64,19 +62,20 @@ namespace Emby.Notifications _appHost = appHost; _config = config; _deviceManager = deviceManager; - _timerFactory = timerFactory; _localization = localization; _activityManager = activityManager; _coreNotificationTypes = new CoreNotificationTypes(localization, appHost).GetNotificationTypes().Select(i => i.Type).ToArray(); } - public void Run() + public Task RunAsync() { _libraryManager.ItemAdded += _libraryManager_ItemAdded; _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; _appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged; _activityManager.EntryCreated += _activityManager_EntryCreated; + + return Task.CompletedTask; } private async void _appHost_HasPendingRestartChanged(object sender, EventArgs e) @@ -157,7 +156,7 @@ namespace Emby.Notifications { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, 5000, + LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, 5000, Timeout.Infinite); } else diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs index aaebe1a21..f3457d105 100644 --- a/Emby.Photos/PhotoProvider.cs +++ b/Emby.Photos/PhotoProvider.cs @@ -181,7 +181,7 @@ namespace Emby.Photos try { - var size = _imageProcessor.GetImageSize(item, img, false); + var size = _imageProcessor.GetImageDimensions(item, img, false); if (size.Width > 0 && size.Height > 0) { diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index efe8f98ec..739f68767 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; @@ -58,7 +59,7 @@ namespace Emby.Server.Implementations.Activity _deviceManager = deviceManager; } - public void Run() + public Task RunAsync() { _taskManager.TaskCompleted += _taskManager_TaskCompleted; @@ -90,6 +91,8 @@ namespace Emby.Server.Implementations.Activity _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + + return Task.CompletedTask; } void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) @@ -207,10 +210,6 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlayback.ToString(); } - if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.GamePlayback.ToString(); - } if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlayback.ToString(); @@ -225,10 +224,6 @@ namespace Emby.Server.Implementations.Activity { return NotificationType.AudioPlaybackStopped.ToString(); } - if (string.Equals(mediaType, MediaType.Game, StringComparison.OrdinalIgnoreCase)) - { - return NotificationType.GamePlaybackStopped.ToString(); - } if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase)) { return NotificationType.VideoPlaybackStopped.ToString(); diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index e4a2cd9df..701c04f9e 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -16,12 +16,14 @@ namespace Emby.Server.Implementations.AppBase string programDataPath, string appFolderPath, string logDirectoryPath = null, - string configurationDirectoryPath = null) + string configurationDirectoryPath = null, + string cacheDirectoryPath = null) { ProgramDataPath = programDataPath; ProgramSystemPath = appFolderPath; LogDirectoryPath = logDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath; + CachePath = cacheDirectoryPath; } public string ProgramDataPath { get; private set; } diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs index 90b43bc59..5feac1adf 100644 --- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs +++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs @@ -171,16 +171,29 @@ namespace Emby.Server.Implementations.AppBase private void UpdateCachePath() { string cachePath; - + // If the configuration file has no entry (i.e. not set in UI) if (string.IsNullOrWhiteSpace(CommonConfiguration.CachePath)) { - cachePath = null; + // If the current live configuration has no entry (i.e. not set on CLI/envvars, during startup) + if (string.IsNullOrWhiteSpace(((BaseApplicationPaths)CommonApplicationPaths).CachePath)) + { + // Set cachePath to a default value under ProgramDataPath + cachePath = Path.Combine(((BaseApplicationPaths)CommonApplicationPaths).ProgramDataPath, "cache"); + } + else + { + // Set cachePath to the existing live value; will require restart if UI value is removed (but not replaced) + // TODO: Figure out how to re-grab this from the CLI/envvars while running + cachePath = ((BaseApplicationPaths)CommonApplicationPaths).CachePath; + } } else { - cachePath = Path.Combine(CommonConfiguration.CachePath, "cache"); + // Set cachePath to the new UI-set value + cachePath = CommonConfiguration.CachePath; } + Logger.LogInformation("Setting cache path to " + cachePath); ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; } @@ -217,7 +230,7 @@ namespace Emby.Server.Implementations.AppBase private string GetConfigurationFile(string key) { - return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLower() + ".xml"); + return Path.Combine(CommonApplicationPaths.ConfigurationDirectoryPath, key.ToLowerInvariant() + ".xml"); } public object GetConfiguration(string key) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 31dad48be..bb475eb2c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -12,7 +12,6 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Common.Implementations.Serialization; using Emby.Dlna; using Emby.Dlna.Main; using Emby.Dlna.Ssdp; @@ -43,7 +42,6 @@ using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; -using Emby.Server.Implementations.Threading; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Xml; @@ -99,7 +97,6 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; using MediaBrowser.Model.Updates; using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Chapters; @@ -110,7 +107,6 @@ using MediaBrowser.XbmcMetadata.Providers; using Microsoft.Extensions.Logging; using ServiceStack; using ServiceStack.Text.Jsv; -using StringExtensions = MediaBrowser.Controller.Extensions.StringExtensions; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -141,7 +137,7 @@ namespace Emby.Server.Implementations return false; } - if (StartupOptions.ContainsOption("-service")) + if (StartupOptions.IsService) { return false; } @@ -302,7 +298,7 @@ namespace Emby.Server.Implementations private ILiveTvManager LiveTvManager { get; set; } - public ILocalizationManager LocalizationManager { get; set; } + public LocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } private IChannelManager ChannelManager { get; set; } @@ -343,12 +339,11 @@ namespace Emby.Server.Implementations protected IHttpResultFactory HttpResultFactory { get; private set; } protected IAuthService AuthService { get; private set; } - public StartupOptions StartupOptions { get; private set; } + public IStartupOptions StartupOptions { get; private set; } internal IImageEncoder ImageEncoder { get; private set; } protected IProcessFactory ProcessFactory { get; private set; } - protected ITimerFactory TimerFactory { get; private set; } protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected readonly IXmlSerializer XmlSerializer; @@ -364,7 +359,7 @@ namespace Emby.Server.Implementations /// public ApplicationHost(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, - StartupOptions options, + IStartupOptions options, IFileSystem fileSystem, IEnvironmentInfo environmentInfo, IImageEncoder imageEncoder, @@ -646,8 +641,10 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// - public Task RunStartupTasks() + public async Task RunStartupTasks() { + Logger.LogInformation("Running startup tasks"); + Resolve().AddTasks(GetExports(false)); ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; @@ -666,20 +663,20 @@ namespace Emby.Server.Implementations Logger.LogInformation("ServerId: {0}", SystemId); var entryPoints = GetExports(); - RunEntryPoints(entryPoints, true); + + var now = DateTime.UtcNow; + await Task.WhenAll(StartEntryPoints(entryPoints, true)); + Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; - Logger.LogInformation("Post-init migrations complete"); - - RunEntryPoints(entryPoints, false); - Logger.LogInformation("All entry points have started"); - - return Task.CompletedTask; + now = DateTime.UtcNow; + await Task.WhenAll(StartEntryPoints(entryPoints, false)); + Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now); } - private void RunEntryPoints(IEnumerable entryPoints, bool isBeforeStartup) + private IEnumerable StartEntryPoints(IEnumerable entryPoints, bool isBeforeStartup) { foreach (var entryPoint in entryPoints) { @@ -688,22 +685,13 @@ namespace Emby.Server.Implementations continue; } - var name = entryPoint.GetType().FullName; - Logger.LogInformation("Starting entry point {Name}", name); - var now = DateTime.UtcNow; - try - { - entryPoint.Run(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error while running entrypoint {Name}", name); - } - Logger.LogInformation("Entry point completed: {Name}. Duration: {Duration} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture), "ImageInfos"); + Logger.LogDebug("Starting entry point {Type}", entryPoint.GetType()); + + yield return entryPoint.RunAsync(); } } - public void Init() + public async Task Init() { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -733,7 +721,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - RegisterResources(); + await RegisterResources(); FindParts(); } @@ -748,7 +736,7 @@ namespace Emby.Server.Implementations /// /// Registers resources that classes will depend on /// - protected void RegisterResources() + protected async Task RegisterResources() { RegisterSingleInstance(ConfigurationManager); RegisterSingleInstance(this); @@ -780,9 +768,6 @@ namespace Emby.Server.Implementations ProcessFactory = new ProcessFactory(); RegisterSingleInstance(ProcessFactory); - TimerFactory = new TimerFactory(); - RegisterSingleInstance(TimerFactory); - var streamHelper = CreateStreamHelper(); ApplicationHost.StreamHelper = streamHelper; RegisterSingleInstance(streamHelper); @@ -809,9 +794,9 @@ namespace Emby.Server.Implementations IAssemblyInfo assemblyInfo = new AssemblyInfo(); RegisterSingleInstance(assemblyInfo); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory, assemblyInfo, new TextLocalizer()); - StringExtensions.LocalizationManager = LocalizationManager; - RegisterSingleInstance(LocalizationManager); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); + await LocalizationManager.LoadAll(); + RegisterSingleInstance(LocalizationManager); BlurayExaminer = new BdInfoExaminer(FileSystemManager); RegisterSingleInstance(BlurayExaminer); @@ -845,7 +830,7 @@ namespace Emby.Server.Implementations var musicManager = new MusicManager(LibraryManager); RegisterSingleInstance(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, EnvironmentInfo); + LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); RegisterSingleInstance(LibraryMonitor); RegisterSingleInstance(() => new SearchEngine(LoggerFactory, LibraryManager, UserManager)); @@ -877,7 +862,7 @@ namespace Emby.Server.Implementations DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager); RegisterSingleInstance(DeviceManager); - MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, TimerFactory, () => MediaEncoder); + MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder); RegisterSingleInstance(MediaSourceManager); SubtitleManager = new SubtitleManager(LoggerFactory, FileSystemManager, LibraryMonitor, MediaSourceManager, LocalizationManager); @@ -892,7 +877,7 @@ namespace Emby.Server.Implementations ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager); RegisterSingleInstance(ChannelManager); - SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager, TimerFactory); + SessionManager = new SessionManager(UserDataManager, LoggerFactory, LibraryManager, UserManager, musicManager, DtoService, ImageProcessor, JsonSerializer, this, HttpClient, AuthenticationRepository, DeviceManager, MediaSourceManager); RegisterSingleInstance(SessionManager); var dlnaManager = new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo); @@ -904,7 +889,7 @@ namespace Emby.Server.Implementations PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LoggerFactory, UserManager, ProviderManager); RegisterSingleInstance(PlaylistManager); - LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, () => ChannelManager); + LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, LoggerFactory, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, FileSystemManager, () => ChannelManager); RegisterSingleInstance(LiveTvManager); UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager); @@ -913,7 +898,7 @@ namespace Emby.Server.Implementations NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager); RegisterSingleInstance(NotificationManager); - RegisterSingleInstance(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory, TimerFactory)); + RegisterSingleInstance(new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory)); ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); RegisterSingleInstance(ChapterManager); @@ -1671,7 +1656,6 @@ namespace Emby.Server.Implementations var minRequiredVersions = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "GameBrowser.dll", new Version(3, 1) }, { "moviethemesongs.dll", new Version(1, 6) }, { "themesongs.dll", new Version(1, 2) } }; @@ -1747,7 +1731,7 @@ namespace Emby.Server.Implementations EncoderLocationType = MediaEncoder.EncoderLocationType, SystemArchitecture = EnvironmentInfo.SystemArchitecture, SystemUpdateLevel = SystemUpdateLevel, - PackageName = StartupOptions.GetOption("-package") + PackageName = StartupOptions.PackageName }; } diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index 6642d1ef4..6aeadda2f 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -70,7 +70,8 @@ namespace Emby.Server.Implementations.Collections return null; }) .Where(i => i != null) - .DistinctBy(i => i.Id) + .GroupBy(x => x.Id) + .Select(x => x.First()) .OrderBy(i => Guid.NewGuid()) .ToList(); } diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index c8b822970..812e48a1f 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -353,7 +353,7 @@ namespace Emby.Server.Implementations.Collections _logger = logger; } - public async void Run() + public async Task RunAsync() { if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted) { diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 3de4da444..3014e482d 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -1239,10 +1239,6 @@ namespace Emby.Server.Implementations.Data { return false; } - else if (type == typeof(GameGenre)) - { - return false; - } else if (type == typeof(Genre)) { return false; @@ -3905,7 +3901,7 @@ namespace Emby.Server.Implementations.Data // lowercase this because SortName is stored as lowercase if (statement != null) { - statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLower()); + statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant()); } } if (!string.IsNullOrWhiteSpace(query.NameLessThan)) @@ -3914,7 +3910,7 @@ namespace Emby.Server.Implementations.Data // lowercase this because SortName is stored as lowercase if (statement != null) { - statement.TryBind("@NameLessThan", query.NameLessThan.ToLower()); + statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant()); } } @@ -4789,10 +4785,6 @@ namespace Emby.Server.Implementations.Data { list.Add(typeof(MusicGenre).Name); } - if (IsTypeInQuery(typeof(GameGenre).Name, query)) - { - list.Add(typeof(GameGenre).Name); - } if (IsTypeInQuery(typeof(MusicArtist).Name, query)) { list.Add(typeof(MusicArtist).Name); @@ -4822,7 +4814,7 @@ namespace Emby.Server.Implementations.Data return value; } - return value.RemoveDiacritics().ToLower(); + return value.RemoveDiacritics().ToLowerInvariant(); } private bool EnableGroupByPresentationUniqueKey(InternalItemsQuery query) @@ -4891,9 +4883,6 @@ namespace Emby.Server.Implementations.Data typeof(Book), typeof(CollectionFolder), typeof(Folder), - typeof(Game), - typeof(GameGenre), - typeof(GameSystem), typeof(Genre), typeof(Person), typeof(Photo), @@ -5251,11 +5240,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName); } - public QueryResult> GetGameGenres(InternalItemsQuery query) - { - return GetItemValues(query, new[] { 2 }, typeof(GameGenre).FullName); - } - public QueryResult> GetMusicGenres(InternalItemsQuery query) { return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName); @@ -5276,14 +5260,9 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type return GetItemValueNames(new[] { 2 }, new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }, new List()); } - public List GetGameGenreNames() - { - return GetItemValueNames(new[] { 2 }, new List { "Game" }, new List()); - } - public List GetGenreNames() { - return GetItemValueNames(new[] { 2 }, new List(), new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist", "Game", "GameSystem" }); + return GetItemValueNames(new[] { 2 }, new List(), new List { "Audio", "MusicVideo", "MusicAlbum", "MusicArtist" }); } private List GetItemValueNames(int[] itemValueTypes, List withItemTypes, List excludeItemTypes) @@ -5652,10 +5631,6 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type { counts.SongCount = value; } - else if (string.Equals(typeName, typeof(Game).FullName, StringComparison.OrdinalIgnoreCase)) - { - counts.GameCount = value; - } else if (string.Equals(typeName, typeof(Trailer).FullName, StringComparison.OrdinalIgnoreCase)) { counts.TrailerCount = value; diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs index e94a649bf..ec3649bca 100644 --- a/Emby.Server.Implementations/Devices/DeviceManager.cs +++ b/Emby.Server.Implementations/Devices/DeviceManager.cs @@ -425,7 +425,7 @@ namespace Emby.Server.Implementations.Devices _logger = logger; } - public async void Run() + public async Task RunAsync() { if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted) { diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 55539eafc..78b22bda3 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Diagnostics { return _process.WaitForExit(timeMs); } - + public Task WaitForExitAsync(int timeMs) { //Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index d0a7de11d..983eb51e6 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -374,10 +374,6 @@ namespace Emby.Server.Implementations.Dto dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); dto.SongCount = taggedItems.Count(i => i is Audio); } - else if (item is GameGenre) - { - dto.GameCount = taggedItems.Count(i => i is Game); - } else { // This populates them all and covers Genre, Person, Studio, Year @@ -385,7 +381,6 @@ namespace Emby.Server.Implementations.Dto dto.ArtistCount = taggedItems.Count(i => i is MusicArtist); dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); dto.EpisodeCount = taggedItems.Count(i => i is Episode); - dto.GameCount = taggedItems.Count(i => i is Game); dto.MovieCount = taggedItems.Count(i => i is Movie); dto.TrailerCount = taggedItems.Count(i => i is Trailer); dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); @@ -532,17 +527,6 @@ namespace Emby.Server.Implementations.Dto dto.Album = item.Album; } - private static void SetGameProperties(BaseItemDto dto, Game item) - { - dto.GameSystem = item.GameSystem; - dto.MultiPartGameFiles = item.MultiPartGameFiles; - } - - private static void SetGameSystemProperties(BaseItemDto dto, GameSystem item) - { - dto.GameSystem = item.GameSystemName; - } - private string[] GetImageTags(BaseItem item, List images) { return images @@ -636,7 +620,8 @@ namespace Emby.Server.Implementations.Dto } }).Where(i => i != null) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); for (var i = 0; i < people.Count; i++) @@ -698,11 +683,6 @@ namespace Emby.Server.Implementations.Dto return _libraryManager.GetMusicGenreId(name); } - if (owner is Game || owner is GameSystem) - { - return _libraryManager.GetGameGenreId(name); - } - return _libraryManager.GetGenreId(name); } @@ -1206,20 +1186,6 @@ namespace Emby.Server.Implementations.Dto } } - var game = item as Game; - - if (game != null) - { - SetGameProperties(dto, game); - } - - var gameSystem = item as GameSystem; - - if (gameSystem != null) - { - SetGameSystemProperties(dto, gameSystem); - } - var musicVideo = item as MusicVideo; if (musicVideo != null) { @@ -1452,7 +1418,7 @@ namespace Emby.Server.Implementations.Dto try { - size = _imageProcessor.GetImageSize(item, imageInfo); + size = _imageProcessor.GetImageDimensions(item, imageInfo); if (size.Width <= 0 || size.Height <= 0) { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index df7963b02..86b2efe54 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,4 +1,4 @@ - + @@ -25,8 +25,7 @@ - - + @@ -43,7 +42,7 @@ - + diff --git a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index 0fc4c3858..19ea09359 100644 --- a/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -9,7 +9,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -22,11 +21,10 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly ILiveTvManager _liveTvManager; - private readonly ITimerFactory _timerFactory; - private ITimer _timer; + private Timer _timer; - public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager, ITimerFactory timerFactory) + public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager) { _appHost = appHost; _logger = logger; @@ -34,15 +32,16 @@ namespace Emby.Server.Implementations.EntryPoints _sessionManager = sessionManager; _config = config; _liveTvManager = liveTvManager; - _timerFactory = timerFactory; } - public void Run() + public Task RunAsync() { if (_appHost.CanSelfRestart) { _appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged; } + + return Task.CompletedTask; } void _appHost_HasPendingRestartChanged(object sender, EventArgs e) @@ -51,7 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints if (_appHost.HasPendingRestart) { - _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15)); + _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15)); } } diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index 8755ee3a7..f26a70586 100644 --- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; using Mono.Nat; @@ -24,19 +23,17 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IServerConfigurationManager _config; private readonly IDeviceDiscovery _deviceDiscovery; - private ITimer _timer; - private readonly ITimerFactory _timerFactory; + private Timer _timer; private NatManager _natManager; - public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, ITimerFactory timerFactory) + public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient) { _logger = loggerFactory.CreateLogger("PortMapper"); _appHost = appHost; _config = config; _deviceDiscovery = deviceDiscovery; _httpClient = httpClient; - _timerFactory = timerFactory; _config.ConfigurationUpdated += _config_ConfigurationUpdated1; } @@ -61,17 +58,17 @@ namespace Emby.Server.Implementations.EntryPoints return string.Join("|", values.ToArray()); } - void _config_ConfigurationUpdated(object sender, EventArgs e) + private async void _config_ConfigurationUpdated(object sender, EventArgs e) { if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase)) { DisposeNat(); - Run(); + await RunAsync(); } } - public void Run() + public Task RunAsync() { if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess) { @@ -80,6 +77,8 @@ namespace Emby.Server.Implementations.EntryPoints _config.ConfigurationUpdated -= _config_ConfigurationUpdated; _config.ConfigurationUpdated += _config_ConfigurationUpdated; + + return Task.CompletedTask; } private void Start() @@ -92,7 +91,7 @@ namespace Emby.Server.Implementations.EntryPoints _natManager.StartDiscovery(); } - _timer = _timerFactory.Create(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); + _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered; diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 7a8b09cf7..038965647 100644 --- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -13,7 +14,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -28,7 +28,6 @@ namespace Emby.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; private readonly ILogger _logger; - private readonly ITimerFactory _timerFactory; /// /// The _library changed sync lock @@ -46,7 +45,7 @@ namespace Emby.Server.Implementations.EntryPoints /// Gets or sets the library update timer. /// /// The library update timer. - private ITimer LibraryUpdateTimer { get; set; } + private Timer LibraryUpdateTimer { get; set; } /// /// The library update duration @@ -55,17 +54,16 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IProviderManager _providerManager; - public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, ITimerFactory timerFactory, IProviderManager providerManager) + public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, IProviderManager providerManager) { _libraryManager = libraryManager; _sessionManager = sessionManager; _userManager = userManager; _logger = logger; - _timerFactory = timerFactory; _providerManager = providerManager; } - public void Run() + public Task RunAsync() { _libraryManager.ItemAdded += libraryManager_ItemAdded; _libraryManager.ItemUpdated += libraryManager_ItemUpdated; @@ -74,6 +72,8 @@ namespace Emby.Server.Implementations.EntryPoints _providerManager.RefreshCompleted += _providerManager_RefreshCompleted; _providerManager.RefreshStarted += _providerManager_RefreshStarted; _providerManager.RefreshProgress += _providerManager_RefreshProgress; + + return Task.CompletedTask; } private Dictionary _lastProgressMessageTimes = new Dictionary(); @@ -188,7 +188,7 @@ namespace Emby.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else @@ -222,7 +222,7 @@ namespace Emby.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else @@ -250,7 +250,7 @@ namespace Emby.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else @@ -277,14 +277,21 @@ namespace Emby.Server.Implementations.EntryPoints lock (_libraryChangedSyncLock) { // Remove dupes in case some were saved multiple times - var foldersAddedTo = _foldersAddedTo.DistinctBy(i => i.Id).ToList(); + var foldersAddedTo = _foldersAddedTo + .GroupBy(x => x.Id) + .Select(x => x.First()) + .ToList(); - var foldersRemovedFrom = _foldersRemovedFrom.DistinctBy(i => i.Id).ToList(); + var foldersRemovedFrom = _foldersRemovedFrom + .GroupBy(x => x.Id) + .Select(x => x.First()) + .ToList(); var itemsUpdated = _itemsUpdated - .Where(i => !_itemsAdded.Contains(i)) - .DistinctBy(i => i.Id) - .ToList(); + .Where(i => !_itemsAdded.Contains(i)) + .GroupBy(x => x.Id) + .Select(x => x.First()) + .ToList(); SendChangeNotifications(_itemsAdded.ToList(), itemsUpdated, _itemsRemoved.ToList(), foldersAddedTo, foldersRemovedFrom, CancellationToken.None); diff --git a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index e37ea96a1..0186da9e1 100644 --- a/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Plugins; @@ -24,12 +25,14 @@ namespace Emby.Server.Implementations.EntryPoints _liveTvManager = liveTvManager; } - public void Run() + public Task RunAsync() { _liveTvManager.TimerCancelled += _liveTvManager_TimerCancelled; _liveTvManager.SeriesTimerCancelled += _liveTvManager_SeriesTimerCancelled; _liveTvManager.TimerCreated += _liveTvManager_TimerCreated; _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; + + return Task.CompletedTask; } private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs e) diff --git a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 92ea3a8f4..091dd6a45 100644 --- a/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Updates; using MediaBrowser.Controller; @@ -49,7 +50,7 @@ namespace Emby.Server.Implementations.EntryPoints _sessionManager = sessionManager; } - public void Run() + public Task RunAsync() { _userManager.UserDeleted += userManager_UserDeleted; _userManager.UserUpdated += userManager_UserUpdated; @@ -65,6 +66,8 @@ namespace Emby.Server.Implementations.EntryPoints _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; _taskManager.TaskCompleted += _taskManager_TaskCompleted; + + return Task.CompletedTask; } void _installationManager_PackageInstalling(object sender, InstallationEventArgs e) diff --git a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs index 05c8b07ab..8be6db87d 100644 --- a/Emby.Server.Implementations/EntryPoints/StartupWizard.cs +++ b/Emby.Server.Implementations/EntryPoints/StartupWizard.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using Emby.Server.Implementations.Browser; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; @@ -32,11 +33,11 @@ namespace Emby.Server.Implementations.EntryPoints /// /// Runs this instance. /// - public void Run() + public Task RunAsync() { if (!_appHost.CanLaunchWebBrowser) { - return; + return Task.CompletedTask; } if (!_config.Configuration.IsStartupWizardCompleted) @@ -47,11 +48,13 @@ namespace Emby.Server.Implementations.EntryPoints { var options = ((ApplicationHost)_appHost).StartupOptions; - if (!options.ContainsOption("-noautorunwebapp")) + if (!options.NoAutoRunWebApp) { BrowserLauncher.OpenWebApp(_appHost); } } + + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs index 2c8246d13..5b90dc1fb 100644 --- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Emby.Server.Implementations.Udp; using MediaBrowser.Controller; using MediaBrowser.Controller.Plugins; @@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.EntryPoints /// /// Runs this instance. /// - public void Run() + public Task RunAsync() { var udpServer = new UdpServer(_logger, _appHost, _json, _socketFactory); @@ -57,6 +58,8 @@ namespace Emby.Server.Implementations.EntryPoints { _logger.LogError(ex, "Failed to start UDP Server"); } + + return Task.CompletedTask; } /// diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 9e71ffceb..774ed09da 100644 --- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.EntryPoints @@ -23,24 +22,24 @@ namespace Emby.Server.Implementations.EntryPoints private readonly IUserManager _userManager; private readonly object _syncLock = new object(); - private ITimer UpdateTimer { get; set; } - private readonly ITimerFactory _timerFactory; + private Timer UpdateTimer { get; set; } private const int UpdateDuration = 500; private readonly Dictionary> _changedItems = new Dictionary>(); - public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory) + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) { _userDataManager = userDataManager; _sessionManager = sessionManager; _logger = logger; _userManager = userManager; - _timerFactory = timerFactory; } - public void Run() + public Task RunAsync() { _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + + return Task.CompletedTask; } void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) @@ -54,7 +53,7 @@ namespace Emby.Server.Implementations.EntryPoints { if (UpdateTimer == null) { - UpdateTimer = _timerFactory.Create(UpdateTimerCallback, null, UpdateDuration, + UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration, Timeout.Infinite); } else @@ -121,7 +120,8 @@ namespace Emby.Server.Implementations.EntryPoints var user = _userManager.GetUserById(userId); var dtoList = changedItems - .DistinctBy(i => i.Id) + .GroupBy(x => x.Id) + .Select(x => x.First()) .Select(i => { var dto = _userDataManager.GetUserDataDto(i, user); diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs index 79a42f294..6167d1eaa 100644 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs @@ -28,10 +28,10 @@ namespace Emby.Server.Implementations.FFMpeg _ffmpegInstallInfo = ffmpegInstallInfo; } - public FFMpegInfo GetFFMpegInfo(StartupOptions options) + public FFMpegInfo GetFFMpegInfo(IStartupOptions options) { - var customffMpegPath = options.GetOption("-ffmpeg"); - var customffProbePath = options.GetOption("-ffprobe"); + var customffMpegPath = options.FFmpegPath; + var customffProbePath = options.FFprobePath; if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath)) { diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index a61247fd1..6ea1bd08e 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -264,7 +264,7 @@ namespace Emby.Server.Implementations.HttpClientManager } var url = options.Url; - var urlHash = url.ToLower().GetMD5().ToString("N"); + var urlHash = url.ToLowerInvariant().GetMD5().ToString("N"); var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash); @@ -374,11 +374,11 @@ namespace Emby.Server.Implementations.HttpClientManager { if (options.LogRequestAsDebug) { - _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url); + _logger.LogDebug("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); } else { - _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(), options.Url); + _logger.LogInformation("HttpClientManager {0}: {1}", httpMethod.ToUpper(CultureInfo.CurrentCulture), options.Url); } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 0ad4d8406..7445fd3c2 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -394,7 +394,7 @@ namespace Emby.Server.Implementations.HttpServer { return contentType == null ? null - : contentType.Split(';')[0].ToLower().Trim(); + : contentType.Split(';')[0].ToLowerInvariant().Trim(); } private static string SerializeToXmlString(object from) diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 12532a497..3668f6a7a 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -9,7 +10,6 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -22,8 +22,7 @@ namespace Emby.Server.Implementations.IO private IServerConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; private readonly List _affectedPaths = new List(); - private ITimer _timer; - private readonly ITimerFactory _timerFactory; + private Timer _timer; private readonly object _timerLock = new object(); public string Path { get; private set; } @@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.IO private readonly IEnvironmentInfo _environmentInfo; private readonly ILibraryManager _libraryManager; - public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1) + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1) { logger.LogDebug("New file refresher created for {0}", path); Path = path; @@ -41,7 +40,6 @@ namespace Emby.Server.Implementations.IO LibraryManager = libraryManager; TaskManager = taskManager; Logger = logger; - _timerFactory = timerFactory; _environmentInfo = environmentInfo; _libraryManager = libraryManager1; AddPath(path); @@ -90,7 +88,7 @@ namespace Emby.Server.Implementations.IO if (_timer == null) { - _timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1)); } else { @@ -146,8 +144,8 @@ namespace Emby.Server.Implementations.IO .Distinct(StringComparer.OrdinalIgnoreCase) .Select(GetAffectedBaseItem) .Where(item => item != null) - .DistinctBy(i => i.Id) - .ToList(); + .GroupBy(x => x.Id) + .Select(x => x.First()); foreach (var item in itemsToRefresh) { diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index dad81c195..607a4d333 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -35,7 +34,7 @@ namespace Emby.Server.Implementations.IO /// /// Any file name ending in any of these will be ignored by the watchers /// - private readonly string[] _alwaysIgnoreFiles = new string[] + private readonly HashSet _alwaysIgnoreFiles = new HashSet(StringComparer.OrdinalIgnoreCase) { "small.jpg", "albumart.jpg", @@ -54,7 +53,7 @@ namespace Emby.Server.Implementations.IO ".actors" }; - private readonly string[] _alwaysIgnoreExtensions = new string[] + private readonly HashSet _alwaysIgnoreExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { // thumbs.db ".db", @@ -134,7 +133,6 @@ namespace Emby.Server.Implementations.IO private IServerConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; - private readonly ITimerFactory _timerFactory; private readonly IEnvironmentInfo _environmentInfo; /// @@ -146,7 +144,6 @@ namespace Emby.Server.Implementations.IO ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) { if (taskManager == null) @@ -159,7 +156,6 @@ namespace Emby.Server.Implementations.IO Logger = loggerFactory.CreateLogger(GetType().Name); ConfigurationManager = configurationManager; _fileSystem = fileSystem; - _timerFactory = timerFactory; _environmentInfo = environmentInfo; } @@ -460,8 +456,8 @@ namespace Emby.Server.Implementations.IO var filename = Path.GetFileName(path); var monitorPath = !string.IsNullOrEmpty(filename) && - !_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) && - !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) && + !_alwaysIgnoreFiles.Contains(filename) && + !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path)) && _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1); // Ignore certain files @@ -545,7 +541,7 @@ namespace Emby.Server.Implementations.IO } } - var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo, LibraryManager); + var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager); newRefresher.Completed += NewRefresher_Completed; _activeRefreshers.Add(newRefresher); } @@ -601,20 +597,26 @@ namespace Emby.Server.Implementations.IO /// public void Dispose() { - _disposed = true; Dispose(true); } /// /// Releases unmanaged and - optionally - managed resources. /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) { - if (dispose) + if (_disposed) + { + return; + } + + if (disposing) { Stop(); } + + _disposed = true; } } @@ -627,9 +629,10 @@ namespace Emby.Server.Implementations.IO _monitor = monitor; } - public void Run() + public Task RunAsync() { _monitor.Start(); + return Task.CompletedTask; } public void Dispose() diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs new file mode 100644 index 000000000..24aaa76c0 --- /dev/null +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -0,0 +1,40 @@ +namespace Emby.Server.Implementations +{ + public interface IStartupOptions + { + /// + /// --ffmpeg + /// + string FFmpegPath { get; } + + /// + /// --ffprobe + /// + string FFprobePath { get; } + + /// + /// --service + /// + bool IsService { get; } + + /// + /// --noautorunwebapp + /// + bool NoAutoRunWebApp { get; } + + /// + /// --package-name + /// + string PackageName { get; } + + /// + /// --restartpath + /// + string RestartPath { get; } + + /// + /// --restartargs + /// + string RestartArgs { get; } + } +} diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 9705d54c9..109c21f18 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.Images { return CreateSquareCollage(item, itemsWithImages, outputPath); } - if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum) + if (item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum) { return CreateSquareCollage(item, itemsWithImages, outputPath); } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index f96a211ec..064006ebd 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -512,7 +512,7 @@ namespace Emby.Server.Implementations.Library if (forceCaseInsensitive || !ConfigurationManager.Configuration.EnableCaseSensitiveItemIds) { - key = key.ToLower(); + key = key.ToLowerInvariant(); } key = type.FullName + key; @@ -869,11 +869,6 @@ namespace Emby.Server.Implementations.Library return GetItemByNameId(MusicGenre.GetPath, name); } - public Guid GetGameGenreId(string name) - { - return GetItemByNameId(GameGenre.GetPath, name); - } - /// /// Gets a Genre /// @@ -894,16 +889,6 @@ namespace Emby.Server.Implementations.Library return CreateItemByName(MusicGenre.GetPath, name, new DtoOptions(true)); } - /// - /// Gets the game genre. - /// - /// The name. - /// Task{GameGenre}. - public GameGenre GetGameGenre(string name) - { - return CreateItemByName(GameGenre.GetPath, name, new DtoOptions(true)); - } - /// /// Gets a Year /// @@ -1370,17 +1355,6 @@ namespace Emby.Server.Implementations.Library return ItemRepository.GetGenres(query); } - public QueryResult> GetGameGenres(InternalItemsQuery query) - { - if (query.User != null) - { - AddUserToQuery(query, query.User); - } - - SetTopParentOrAncestorIds(query); - return ItemRepository.GetGameGenres(query); - } - public QueryResult> GetMusicGenres(InternalItemsQuery query) { if (query.User != null) diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs index b83fb9b39..24ab8e761 100644 --- a/Emby.Server.Implementations/Library/MediaSourceManager.cs +++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs @@ -20,7 +20,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Library @@ -36,7 +35,6 @@ namespace Emby.Server.Implementations.Library private IMediaSourceProvider[] _providers; private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; - private readonly ITimerFactory _timerFactory; private readonly Func _mediaEncoder; private ILocalizationManager _localizationManager; private IApplicationPaths _appPaths; @@ -51,7 +49,6 @@ namespace Emby.Server.Implementations.Library IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager, - ITimerFactory timerFactory, Func mediaEncoder) { _itemRepo = itemRepo; @@ -61,7 +58,6 @@ namespace Emby.Server.Implementations.Library _jsonSerializer = jsonSerializer; _fileSystem = fileSystem; _userDataManager = userDataManager; - _timerFactory = timerFactory; _mediaEncoder = mediaEncoder; _localizationManager = localizationManager; _appPaths = applicationPaths; @@ -322,18 +318,18 @@ namespace Emby.Server.Implementations.Library private string[] NormalizeLanguage(string language) { - if (language != null) + if (language == null) { - var culture = _localizationManager.FindLanguageInfo(language); - if (culture != null) - { - return culture.ThreeLetterISOLanguageNames; - } - - return new string[] { language }; + return Array.Empty(); } - return Array.Empty(); + var culture = _localizationManager.FindLanguageInfo(language); + if (culture != null) + { + return culture.ThreeLetterISOLanguageNames; + } + + return new string[] { language }; } private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs index 9de766767..a3298c580 100644 --- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Controller.Drawing; @@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - private static readonly string[] IgnoreFiles = + private static readonly HashSet IgnoreFiles = new HashSet(StringComparer.OrdinalIgnoreCase) { "folder", "thumb", @@ -102,7 +103,7 @@ namespace Emby.Server.Implementations.Library.Resolvers { var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty; - if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase)) + if (IgnoreFiles.Contains(filename)) { return false; } @@ -112,7 +113,7 @@ namespace Emby.Server.Implementations.Library.Resolvers return false; } - return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase); + return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.')); } } diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs index 71638b197..9c7f7dfcb 100644 --- a/Emby.Server.Implementations/Library/SearchEngine.cs +++ b/Emby.Server.Implementations/Library/SearchEngine.cs @@ -99,14 +99,12 @@ namespace Emby.Server.Implementations.Library if (!query.IncludeMedia) { AddIfMissing(includeItemTypes, typeof(Genre).Name); - AddIfMissing(includeItemTypes, typeof(GameGenre).Name); AddIfMissing(includeItemTypes, typeof(MusicGenre).Name); } } else { AddIfMissing(excludeItemTypes, typeof(Genre).Name); - AddIfMissing(excludeItemTypes, typeof(GameGenre).Name); AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name); } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 70639dad5..40eda52c6 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -24,7 +24,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Connect; using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -212,11 +211,8 @@ namespace Emby.Server.Implementations.Library { foreach (var user in users) { - if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value == UserLinkType.LinkedUser) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } + user.Policy.IsAdministrator = true; + UpdateUserPolicy(user, user.Policy, false); } } } @@ -272,13 +268,9 @@ namespace Emby.Server.Implementations.Library if (user != null) { - // Authenticate using local credentials if not a guest - if (!user.ConnectLinkType.HasValue || user.ConnectLinkType.Value != UserLinkType.Guest) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - } + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; } else { @@ -455,30 +447,30 @@ namespace Emby.Server.Implementations.Library private void UpdateInvalidLoginAttemptCount(User user, int newValue) { - if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0) + if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) { - user.Policy.InvalidLoginAttemptCount = newValue; + return; + } - var maxCount = user.Policy.IsAdministrator ? 3 : 5; + user.Policy.InvalidLoginAttemptCount = newValue; - // TODO: Fix - /* - var fireLockout = false; + var maxCount = user.Policy.IsAdministrator ? 3 : 5; - if (newValue >= maxCount) - { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture)); - user.Policy.IsDisabled = true; + var fireLockout = false; - fireLockout = true; - }*/ + if (newValue >= maxCount) + { + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); + user.Policy.IsDisabled = true; - UpdateUserPolicy(user, user.Policy, false); + fireLockout = true; + } - /* if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - }*/ + UpdateUserPolicy(user, user.Policy, false); + + if (fireLockout) + { + UserLockedOut?.Invoke(this, new GenericEventArgs(user)); } } @@ -553,9 +545,6 @@ namespace Emby.Server.Implementations.Library LastActivityDate = user.LastActivityDate, LastLoginDate = user.LastLoginDate, Configuration = user.Configuration, - ConnectLinkType = user.ConnectLinkType, - ConnectUserId = user.ConnectUserId, - ConnectUserName = user.ConnectUserName, ServerId = _appHost.SystemId, Policy = user.Policy }; @@ -814,11 +803,6 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(user)); } - if (user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) - { - throw new ArgumentException("Passwords for guests cannot be changed."); - } - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); UpdateUser(user); @@ -925,11 +909,6 @@ namespace Emby.Server.Implementations.Library null : GetUserByName(enteredUsername); - if (user != null && user.ConnectLinkType.HasValue && user.ConnectLinkType.Value == UserLinkType.Guest) - { - throw new ArgumentException("Unable to process forgot password request for guests."); - } - var action = ForgotPasswordAction.InNetworkRequired; string pinFile = null; DateTime? expirationDate = null; @@ -974,10 +953,7 @@ namespace Emby.Server.Implementations.Library _lastPin = null; _lastPasswordPinCreationResult = null; - var users = Users.Where(i => !i.ConnectLinkType.HasValue || i.ConnectLinkType.Value != UserLinkType.Guest) - .ToList(); - - foreach (var user in users) + foreach (var user in Users) { await ResetPassword(user).ConfigureAwait(false); @@ -1205,9 +1181,11 @@ namespace Emby.Server.Implementations.Library _sessionManager = sessionManager; } - public void Run() + public Task RunAsync() { _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + + return Task.CompletedTask; } private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs index 9fa859bde..e9ce682ee 100644 --- a/Emby.Server.Implementations/Library/UserViewManager.cs +++ b/Emby.Server.Implementations/Library/UserViewManager.cs @@ -308,9 +308,6 @@ namespace Emby.Server.Implementations.Library mediaTypes.Add(MediaType.Book); mediaTypes.Add(MediaType.Audio); break; - case CollectionType.Games: - mediaTypes.Add(MediaType.Game); - break; case CollectionType.Music: mediaTypes.Add(MediaType.Audio); break; @@ -336,7 +333,6 @@ namespace Emby.Server.Implementations.Library typeof(Person).Name, typeof(Studio).Name, typeof(Year).Name, - typeof(GameGenre).Name, typeof(MusicGenre).Name, typeof(Genre).Name diff --git a/Emby.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs deleted file mode 100644 index 2b067951d..000000000 --- a/Emby.Server.Implementations/Library/Validators/GameGenresPostScanTask.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library.Validators -{ - /// - /// Class GameGenresPostScanTask - /// - public class GameGenresPostScanTask : ILibraryPostScanTask - { - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - private readonly ILogger _logger; - private readonly IItemRepository _itemRepo; - - /// - /// Initializes a new instance of the class. - /// - /// The library manager. - /// The logger. - public GameGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } - - /// - /// Runs the specified progress. - /// - /// The progress. - /// The cancellation token. - /// Task. - public Task Run(IProgress progress, CancellationToken cancellationToken) - { - return new GameGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken); - } - } -} diff --git a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs b/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs deleted file mode 100644 index f5ffa1e45..000000000 --- a/Emby.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library.Validators -{ - class GameGenresValidator - { - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The _logger - /// - private readonly ILogger _logger; - private readonly IItemRepository _itemRepo; - - public GameGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo) - { - _libraryManager = libraryManager; - _logger = logger; - _itemRepo = itemRepo; - } - - /// - /// Runs the specified progress. - /// - /// The progress. - /// The cancellation token. - /// Task. - public async Task Run(IProgress progress, CancellationToken cancellationToken) - { - var names = _itemRepo.GetGameGenreNames(); - - var numComplete = 0; - var count = names.Count; - - foreach (var name in names) - { - try - { - var item = _libraryManager.GetGameGenre(name); - - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // Don't clutter the log - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error refreshing {GenreName}", name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 100; - - progress.Report(percent); - } - - progress.Report(100); - } - } -} diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6a2a46c9f..84ca130b7 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -35,8 +35,6 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV @@ -65,7 +63,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public static EmbyTV Current; - public event EventHandler DataSourceChanged; public event EventHandler> TimerCreated; public event EventHandler> TimerCancelled; @@ -88,7 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ILibraryMonitor libraryMonitor, IProviderManager providerManager, IMediaEncoder mediaEncoder, - ITimerFactory timerFactory, IProcessFactory processFactory) { Current = this; @@ -110,7 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _streamHelper = streamHelper; _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger, timerFactory); + _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger); _timerProvider.TimerFired += _timerProvider_TimerFired; _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; @@ -124,7 +120,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - public async void Start() + public async Task Start() { _timerProvider.RestartTimers(); @@ -275,7 +271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV foreach (var timer in seriesTimers) { - await UpdateTimersForSeriesTimer(timer, false, true).ConfigureAwait(false); + UpdateTimersForSeriesTimer(timer, false, true); } } @@ -763,12 +759,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _timerProvider.AddOrUpdate(timer, false); } - await UpdateTimersForSeriesTimer(info, true, false).ConfigureAwait(false); + UpdateTimersForSeriesTimer(info, true, false); return info.Id; } - public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) + public Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken) { var instance = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, info.Id, StringComparison.OrdinalIgnoreCase)); @@ -792,8 +788,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _seriesTimerProvider.Update(instance); - await UpdateTimersForSeriesTimer(instance, true, true).ConfigureAwait(false); + UpdateTimersForSeriesTimer(instance, true, true); } + + return Task.CompletedTask; } public Task UpdateTimerAsync(TimerInfo updatedTimer, CancellationToken cancellationToken) @@ -2193,7 +2191,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV if (lockData) { - writer.WriteElementString("lockdata", true.ToString().ToLower()); + writer.WriteElementString("lockdata", true.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); } if (item.CriticRating.HasValue) @@ -2346,10 +2344,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV } } - private async Task UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) + private void UpdateTimersForSeriesTimer(SeriesTimerInfo seriesTimer, bool updateTimerSettings, bool deleteInvalidTimers) { - var allTimers = GetTimersForSeries(seriesTimer) - .ToList(); + var allTimers = GetTimersForSeries(seriesTimer).ToList(); var enabledTimersForSeries = new List(); diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs index 982a54b68..9c9ba09f5 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs @@ -1,12 +1,13 @@ +using System.Threading.Tasks; using MediaBrowser.Controller.Plugins; namespace Emby.Server.Implementations.LiveTv.EmbyTV { public class EntryPoint : IServerEntryPoint { - public void Run() + public Task RunAsync() { - EmbyTV.Current.Start(); + return EmbyTV.Current.Start(); } public void Dispose() diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs index 7f67d70a9..1dcb02f43 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs @@ -2,29 +2,27 @@ using System; using System.Collections.Concurrent; using System.Globalization; using System.Linq; +using System.Threading; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.LiveTv.EmbyTV { public class TimerManager : ItemDataProvider { - private readonly ConcurrentDictionary _timers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _timers = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ILogger _logger; public event EventHandler> TimerFired; - private readonly ITimerFactory _timerFactory; - public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1, ITimerFactory timerFactory) + public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1) : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase)) { _logger = logger1; - _timerFactory = timerFactory; } public void RestartTimers() @@ -125,7 +123,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private void StartTimer(TimerInfo item, TimeSpan dueTime) { - var timer = _timerFactory.Create(TimerCallback, item.Id, dueTime, TimeSpan.Zero); + var timer = new Timer(TimerCallback, item.Id, dueTime, TimeSpan.Zero); if (_timers.TryAdd(item.Id, timer)) { diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs index f152ac465..69b10e6da 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs @@ -17,7 +17,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using Microsoft.Extensions.Logging; -namespace Jellyfin.Server.Implementations.LiveTv.Listings +namespace Emby.Server.Implementations.LiveTv.Listings { public class XmlTvListingsProvider : IListingsProvider { diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs index 724e8afdf..1144c9ab1 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -399,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv { var name = serviceName + externalId + InternalVersionNumber; - return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvChannel)); + return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel)); } private const string ServiceName = "Emby"; @@ -407,21 +407,21 @@ namespace Emby.Server.Implementations.LiveTv { var name = ServiceName + externalId + InternalVersionNumber; - return name.ToLower().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N"); } public Guid GetInternalSeriesTimerId(string externalId) { var name = ServiceName + externalId + InternalVersionNumber; - return name.ToLower().GetMD5(); + return name.ToLowerInvariant().GetMD5(); } public Guid GetInternalProgramId(string externalId) { var name = ServiceName + externalId + InternalVersionNumber; - return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvProgram)); + return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvProgram)); } public async Task GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs index 575cb1c77..a36302876 100644 --- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Library; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; @@ -24,7 +23,6 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; @@ -48,7 +46,6 @@ namespace Emby.Server.Implementations.LiveTv private readonly ILibraryManager _libraryManager; private readonly ITaskManager _taskManager; private readonly IJsonSerializer _jsonSerializer; - private readonly IProviderManager _providerManager; private readonly Func _channelManager; private readonly IDtoService _dtoService; @@ -56,7 +53,7 @@ namespace Emby.Server.Implementations.LiveTv private readonly LiveTvDtoService _tvDtoService; - private ILiveTvService[] _services = new ILiveTvService[] { }; + private ILiveTvService[] _services = Array.Empty(); private ITunerHost[] _tunerHosts = Array.Empty(); private IListingsProvider[] _listingProviders = Array.Empty(); @@ -85,7 +82,6 @@ namespace Emby.Server.Implementations.LiveTv ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, - IProviderManager providerManager, IFileSystem fileSystem, Func channelManager) { @@ -97,7 +93,6 @@ namespace Emby.Server.Implementations.LiveTv _taskManager = taskManager; _localization = localization; _jsonSerializer = jsonSerializer; - _providerManager = providerManager; _fileSystem = fileSystem; _dtoService = dtoService; _userDataManager = userDataManager; @@ -132,8 +127,6 @@ namespace Emby.Server.Implementations.LiveTv foreach (var service in _services) { - service.DataSourceChanged += service_DataSourceChanged; - if (service is EmbyTV.EmbyTV embyTv) { embyTv.TimerCreated += EmbyTv_TimerCreated; @@ -189,14 +182,6 @@ namespace Emby.Server.Implementations.LiveTv return EmbyTV.EmbyTV.Current.DiscoverTuners(newDevicesOnly, cancellationToken); } - void service_DataSourceChanged(object sender, EventArgs e) - { - if (!_isDisposed) - { - _taskManager.CancelIfRunningAndQueue(); - } - } - public QueryResult GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken) { var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId); @@ -2158,17 +2143,28 @@ namespace Emby.Server.Implementations.LiveTv Dispose(true); } - private bool _isDisposed = false; + private bool _disposed = false; /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { - _isDisposed = true; + // TODO: Dispose stuff } + + _services = null; + _listingProviders = null; + _tunerHosts = null; + + _disposed = true; } private LiveTvServiceInfo[] GetServiceInfos() @@ -2469,7 +2465,8 @@ namespace Emby.Server.Implementations.LiveTv .Where(i => i != null) .Where(i => i.IsVisibleStandalone(user)) .SelectMany(i => _libraryManager.GetCollectionFolders(i)) - .DistinctBy(i => i.Id) + .GroupBy(x => x.Id) + .Select(x => x.First()) .OrderBy(i => i.SortName) .ToList(); diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs index 716417ccb..4eff9252e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs @@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts var now = DateTime.UtcNow; - StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); + var _ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token); //OpenedMediaSource.Protocol = MediaProtocol.File; //OpenedMediaSource.Path = tempFile; diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index ec2c3f237..eb145b4fe 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", "Favorites": "المفضلات", "Folders": "المجلدات", - "Games": "الألعاب", "Genres": "أنواع الأفلام", "HeaderAlbumArtists": "فنانو الألبومات", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", "NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي", "NotificationOptionCameraImageUploaded": "تم رقع صورة الكاميرا", - "NotificationOptionGamePlayback": "تم تشغيل اللعبة", - "NotificationOptionGamePlaybackStopped": "تم إيقاف تشغيل اللعبة", "NotificationOptionInstallationFailed": "عملية التنصيب فشلت", "NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد", "NotificationOptionPluginError": "فشل في الملحق", diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index ba6c98555..a71dc9346 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Любими", "Folders": "Папки", - "Games": "Игри", "Genres": "Жанрове", "HeaderAlbumArtists": "Изпълнители на албуми", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна", "NotificationOptionAudioPlaybackStopped": "Възпроизвеждането на звук е спряно", "NotificationOptionCameraImageUploaded": "Изображението от фотоапарата е качено", - "NotificationOptionGamePlayback": "Възпроизвеждането на играта започна", - "NotificationOptionGamePlaybackStopped": "Възпроизвеждането на играта е спряна", "NotificationOptionInstallationFailed": "Неуспешно инсталиране", "NotificationOptionNewLibraryContent": "Добавено е ново съдържание", "NotificationOptionPluginError": "Грешка в приставка", diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json index a818b78de..74406a064 100644 --- a/Emby.Server.Implementations/Localization/Core/ca.json +++ b/Emby.Server.Implementations/Localization/Core/ca.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}", "Favorites": "Preferits", "Folders": "Directoris", - "Games": "Jocs", "Genres": "Gèneres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Un component ha fallat", diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json index e066051a8..a8b4a4424 100644 --- a/Emby.Server.Implementations/Localization/Core/cs.json +++ b/Emby.Server.Implementations/Localization/Core/cs.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Neúspěšný pokus o přihlášení z {0}", "Favorites": "Oblíbené", "Folders": "Složky", - "Games": "Hry", "Genres": "Žánry", "HeaderAlbumArtists": "Umělci alba", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Přehrávání audia zahájeno", "NotificationOptionAudioPlaybackStopped": "Přehrávání audia ukončeno", "NotificationOptionCameraImageUploaded": "Kamerový záznam nahrán", - "NotificationOptionGamePlayback": "Spuštění hry zahájeno", - "NotificationOptionGamePlaybackStopped": "Hra ukončena", "NotificationOptionInstallationFailed": "Chyba instalace", "NotificationOptionNewLibraryContent": "Přidán nový obsah", "NotificationOptionPluginError": "Chyba zásuvného modulu", diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json index 30581c389..7004d44db 100644 --- a/Emby.Server.Implementations/Localization/Core/da.json +++ b/Emby.Server.Implementations/Localization/Core/da.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}", "Favorites": "Favoritter", "Folders": "Mapper", - "Games": "Spil", "Genres": "Genre", "HeaderAlbumArtists": "Albumkunstnere", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audioafspilning påbegyndt", "NotificationOptionAudioPlaybackStopped": "Audioafspilning stoppet", "NotificationOptionCameraImageUploaded": "Kamerabillede uploadet", - "NotificationOptionGamePlayback": "Afspilning af Spil påbegyndt", - "NotificationOptionGamePlaybackStopped": "Afspilning af Spil stoppet", "NotificationOptionInstallationFailed": "Installationsfejl", "NotificationOptionNewLibraryContent": "Nyt indhold tilføjet", "NotificationOptionPluginError": "Pluginfejl", diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json index 98cb07663..7bd2e90fe 100644 --- a/Emby.Server.Implementations/Localization/Core/de.json +++ b/Emby.Server.Implementations/Localization/Core/de.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}", "Favorites": "Favoriten", "Folders": "Verzeichnisse", - "Games": "Spiele", "Genres": "Genres", "HeaderAlbumArtists": "Album-Künstler", "HeaderCameraUploads": "Kamera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audiowiedergabe gestartet", "NotificationOptionAudioPlaybackStopped": "Audiowiedergabe gestoppt", "NotificationOptionCameraImageUploaded": "Kamera Bild hochgeladen", - "NotificationOptionGamePlayback": "Spielwiedergabe gestartet", - "NotificationOptionGamePlaybackStopped": "Spielwiedergabe gestoppt", "NotificationOptionInstallationFailed": "Installationsfehler", "NotificationOptionNewLibraryContent": "Neuer Inhalt hinzugefügt", "NotificationOptionPluginError": "Plugin Fehler", diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json index ba687a089..91ca34edc 100644 --- a/Emby.Server.Implementations/Localization/Core/el.json +++ b/Emby.Server.Implementations/Localization/Core/el.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}", "Favorites": "Αγαπημένα", "Folders": "Φάκελοι", - "Games": "Παιχνίδια", "Genres": "Είδη", "HeaderAlbumArtists": "Άλμπουμ Καλλιτεχνών", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Η αναπαραγωγή ήχου ξεκίνησε", "NotificationOptionAudioPlaybackStopped": "Η αναπαραγωγή ήχου σταμάτησε", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Η αναπαραγωγή του παιχνιδιού ξεκίνησε", - "NotificationOptionGamePlaybackStopped": "Η αναπαραγωγή του παιχνιδιού σταμάτησε", "NotificationOptionInstallationFailed": "Αποτυχία εγκατάστασης", "NotificationOptionNewLibraryContent": "Προστέθηκε νέο περιεχόμενο", "NotificationOptionPluginError": "Αποτυχία του plugin", diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json index 20d397a1a..332902292 100644 --- a/Emby.Server.Implementations/Localization/Core/en-GB.json +++ b/Emby.Server.Implementations/Localization/Core/en-GB.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favourites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/en-US.json b/Emby.Server.Implementations/Localization/Core/en-US.json index 69c8bf03c..f19cd532b 100644 --- a/Emby.Server.Implementations/Localization/Core/en-US.json +++ b/Emby.Server.Implementations/Localization/Core/en-US.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index aaaf09788..c01bb0c50 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 2ba9c8c7a..2285f2808 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}", "Favorites": "Favoritos", "Folders": "Carpetas", - "Games": "Juegos", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del Álbum", "HeaderCameraUploads": "Subidos desde Camara", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", - "NotificationOptionGamePlayback": "Ejecución de juego iniciada", - "NotificationOptionGamePlaybackStopped": "Ejecución de juego detenida", "NotificationOptionInstallationFailed": "Falla de instalación", "NotificationOptionNewLibraryContent": "Nuevo contenido agregado", "NotificationOptionPluginError": "Falla de complemento", diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json index 38a282382..5d118d21f 100644 --- a/Emby.Server.Implementations/Localization/Core/es.json +++ b/Emby.Server.Implementations/Localization/Core/es.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión a partir de {0}", "Favorites": "Favoritos", "Folders": "Carpetas", - "Games": "Juegos", "Genres": "Géneros", "HeaderAlbumArtists": "Artistas del Álbum", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", "NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio", "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada", - "NotificationOptionGamePlayback": "Se inició la reproducción del juego", - "NotificationOptionGamePlaybackStopped": "Se detuvo la reproducción del juego", "NotificationOptionInstallationFailed": "Error de instalación", "NotificationOptionNewLibraryContent": "Nuevo contenido añadido", "NotificationOptionPluginError": "Error en plugin", diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json index 1d7bc5fe8..0a0c7553b 100644 --- a/Emby.Server.Implementations/Localization/Core/fa.json +++ b/Emby.Server.Implementations/Localization/Core/fa.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود", "Favorites": "مورد علاقه ها", "Folders": "پوشه ها", - "Games": "بازی ها", "Genres": "ژانرها", "HeaderAlbumArtists": "هنرمندان آلبوم", "HeaderCameraUploads": "آپلودهای دوربین", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "پخش صدا آغاز شد", "NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد", "NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد", - "NotificationOptionGamePlayback": "پخش بازی آغاز شد", - "NotificationOptionGamePlaybackStopped": "پخش بازی متوقف شد", "NotificationOptionInstallationFailed": "شکست نصب", "NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد", "NotificationOptionPluginError": "خرابی افزونه", diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 22cd4cf6d..7202be9f5 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 085e22cf7..aa9a1add3 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Échec d'une tentative de connexion de {0}", "Favorites": "Favoris", "Folders": "Dossiers", - "Games": "Jeux", "Genres": "Genres", "HeaderAlbumArtists": "Artistes de l'album", "HeaderCameraUploads": "Photos transférées", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Lecture audio démarrée", "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée", - "NotificationOptionGamePlayback": "Lecture de jeu démarrée", - "NotificationOptionGamePlaybackStopped": "Lecture de jeu arrêtée", "NotificationOptionInstallationFailed": "Échec d'installation", "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", "NotificationOptionPluginError": "Erreur d'extension", diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json index 537fe35d5..728002a56 100644 --- a/Emby.Server.Implementations/Localization/Core/gsw.json +++ b/Emby.Server.Implementations/Localization/Core/gsw.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Spiel", "Genres": "Genres", "HeaderAlbumArtists": "Albuminterprete", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index 6fff9d0ab..fff1d1f0e 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "משחקים", "Genres": "ז'אנרים", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index 3232a4ff7..f284b3cd9 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave za {0}", "Favorites": "Omiljeni", "Folders": "Mape", - "Games": "Igre", "Genres": "Žanrovi", "HeaderAlbumArtists": "Izvođači albuma", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Reprodukcija glazbe započeta", "NotificationOptionAudioPlaybackStopped": "Reprodukcija audiozapisa je zaustavljena", "NotificationOptionCameraImageUploaded": "Slike kamere preuzete", - "NotificationOptionGamePlayback": "Igrica pokrenuta", - "NotificationOptionGamePlaybackStopped": "Reprodukcija igre je zaustavljena", "NotificationOptionInstallationFailed": "Instalacija nije izvršena", "NotificationOptionNewLibraryContent": "Novi sadržaj je dodan", "NotificationOptionPluginError": "Dodatak otkazao", diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json index 3a0119faf..d2d16b18f 100644 --- a/Emby.Server.Implementations/Localization/Core/hu.json +++ b/Emby.Server.Implementations/Localization/Core/hu.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Kedvencek", "Folders": "Könyvtárak", - "Games": "Játékok", "Genres": "Műfajok", "HeaderAlbumArtists": "Album Előadók", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audió lejátszás elkezdve", "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve", "NotificationOptionCameraImageUploaded": "Kamera kép feltöltve", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Telepítési hiba", "NotificationOptionNewLibraryContent": "Új tartalom hozzáadva", "NotificationOptionPluginError": "Bővítmény hiba", diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index 58b7bb61a..b3d9c16cf 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}", "Favorites": "Preferiti", "Folders": "Cartelle", - "Games": "Giochi", "Genres": "Generi", "HeaderAlbumArtists": "Artisti Album", "HeaderCameraUploads": "Caricamenti Fotocamera", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "La riproduzione audio è iniziata", "NotificationOptionAudioPlaybackStopped": "La riproduzione audio è stata interrotta", "NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata", - "NotificationOptionGamePlayback": "Il gioco è stato avviato", - "NotificationOptionGamePlaybackStopped": "Il gioco è stato fermato", "NotificationOptionInstallationFailed": "Installazione fallita", "NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto", "NotificationOptionPluginError": "Errore del Plug-in", diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 45b8617f1..ae256f79d 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "{0} тарапынан кіру әрекеті сәтсіз", "Favorites": "Таңдаулылар", "Folders": "Қалталар", - "Games": "Ойындар", "Genres": "Жанрлар", "HeaderAlbumArtists": "Альбом орындаушылары", "HeaderCameraUploads": "Камерадан жүктелгендер", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Дыбыс ойнатуы басталды", "NotificationOptionAudioPlaybackStopped": "Дыбыс ойнатуы тоқтатылды", "NotificationOptionCameraImageUploaded": "Камерадан фотосурет кері қотарылған", - "NotificationOptionGamePlayback": "Ойын ойнатуы басталды", - "NotificationOptionGamePlaybackStopped": "Ойын ойнатуы тоқтатылды", "NotificationOptionInstallationFailed": "Орнату сәтсіздігі", "NotificationOptionNewLibraryContent": "Жаңа мазмұн үстелген", "NotificationOptionPluginError": "Плагин сәтсіздігі", diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json index 04fc52d6e..21808fd18 100644 --- a/Emby.Server.Implementations/Localization/Core/ko.json +++ b/Emby.Server.Implementations/Localization/Core/ko.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "앨범 아티스트", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json index 653565db6..558904f06 100644 --- a/Emby.Server.Implementations/Localization/Core/lt-LT.json +++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Žanrai", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json index aaaf09788..c01bb0c50 100644 --- a/Emby.Server.Implementations/Localization/Core/ms.json +++ b/Emby.Server.Implementations/Localization/Core/ms.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json index ed63aa29c..dbda794ad 100644 --- a/Emby.Server.Implementations/Localization/Core/nb.json +++ b/Emby.Server.Implementations/Localization/Core/nb.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Mislykket påloggingsforsøk fra {0}", "Favorites": "Favoritter", "Folders": "Mapper", - "Games": "Spill", "Genres": "Sjanger", "HeaderAlbumArtists": "Albumartist", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Lyd tilbakespilling startet", "NotificationOptionAudioPlaybackStopped": "Lyd avspilling stoppet", "NotificationOptionCameraImageUploaded": "Kamera bilde lastet opp", - "NotificationOptionGamePlayback": "Spill avspillingen startet", - "NotificationOptionGamePlaybackStopped": "Filmer", "NotificationOptionInstallationFailed": "Installasjon feil", "NotificationOptionNewLibraryContent": "Ny innhold er lagt til", "NotificationOptionPluginError": "Plugin feil", diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json index 7b8c8765e..589753c44 100644 --- a/Emby.Server.Implementations/Localization/Core/nl.json +++ b/Emby.Server.Implementations/Localization/Core/nl.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Mislukte aanmeld poging van {0}", "Favorites": "Favorieten", "Folders": "Mappen", - "Games": "Spellen", "Genres": "Genres", "HeaderAlbumArtists": "Album artiesten", "HeaderCameraUploads": "Camera uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Geluid gestart", "NotificationOptionAudioPlaybackStopped": "Geluid gestopt", "NotificationOptionCameraImageUploaded": "Camera afbeelding geüpload", - "NotificationOptionGamePlayback": "Spel gestart", - "NotificationOptionGamePlaybackStopped": "Spel gestopt", "NotificationOptionInstallationFailed": "Installatie mislukt", "NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd", "NotificationOptionPluginError": "Plug-in fout", diff --git a/Emby.Server.Implementations/Localization/Core/pl.json b/Emby.Server.Implementations/Localization/Core/pl.json index 5aefa740b..92b12409d 100644 --- a/Emby.Server.Implementations/Localization/Core/pl.json +++ b/Emby.Server.Implementations/Localization/Core/pl.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Próba logowania przez {0} zakończona niepowodzeniem", "Favorites": "Ulubione", "Folders": "Foldery", - "Games": "Gry", "Genres": "Gatunki", "HeaderAlbumArtists": "Wykonawcy albumów", "HeaderCameraUploads": "Przekazane obrazy", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Rozpoczęto odtwarzanie muzyki", "NotificationOptionAudioPlaybackStopped": "Odtwarzane dźwięku zatrzymane", "NotificationOptionCameraImageUploaded": "Przekazano obraz z urządzenia mobilnego", - "NotificationOptionGamePlayback": "Odtwarzanie gry rozpoczęte", - "NotificationOptionGamePlaybackStopped": "Odtwarzanie gry zatrzymane", "NotificationOptionInstallationFailed": "Niepowodzenie instalacji", "NotificationOptionNewLibraryContent": "Dodano nową zawartość", "NotificationOptionPluginError": "Awaria wtyczki", diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index 9ae25d3ac..aaedf0850 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Falha na tentativa de login de {0}", "Favorites": "Favoritos", "Folders": "Pastas", - "Games": "Jogos", "Genres": "Gêneros", "HeaderAlbumArtists": "Artistas do Álbum", "HeaderCameraUploads": "Uploads da Câmera", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Reprodução de áudio iniciada", "NotificationOptionAudioPlaybackStopped": "Reprodução de áudio parada", "NotificationOptionCameraImageUploaded": "Imagem de câmera enviada", - "NotificationOptionGamePlayback": "Reprodução de jogo iniciada", - "NotificationOptionGamePlaybackStopped": "Reprodução de jogo parada", "NotificationOptionInstallationFailed": "Falha na instalação", "NotificationOptionNewLibraryContent": "Novo conteúdo adicionado", "NotificationOptionPluginError": "Falha de plugin", diff --git a/Emby.Server.Implementations/Localization/Core/pt-PT.json b/Emby.Server.Implementations/Localization/Core/pt-PT.json index 59c25f0e3..dc69d8af2 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-PT.json +++ b/Emby.Server.Implementations/Localization/Core/pt-PT.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json index 338141294..d799fa50b 100644 --- a/Emby.Server.Implementations/Localization/Core/ru.json +++ b/Emby.Server.Implementations/Localization/Core/ru.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна", "Favorites": "Избранное", "Folders": "Папки", - "Games": "Игры", "Genres": "Жанры", "HeaderAlbumArtists": "Исп-ли альбома", "HeaderCameraUploads": "Камеры", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но", "NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но", "NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры", - "NotificationOptionGamePlayback": "Воспр-ие игры зап-но", - "NotificationOptionGamePlaybackStopped": "Восп-ие игры ост-но", "NotificationOptionInstallationFailed": "Сбой установки", "NotificationOptionNewLibraryContent": "Новое содержание добавлено", "NotificationOptionPluginError": "Сбой плагина", diff --git a/Emby.Server.Implementations/Localization/Core/sk.json b/Emby.Server.Implementations/Localization/Core/sk.json index ed0c68952..bc7e7184e 100644 --- a/Emby.Server.Implementations/Localization/Core/sk.json +++ b/Emby.Server.Implementations/Localization/Core/sk.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Neúspešný pokus o prihlásenie z {0}", "Favorites": "Obľúbené", "Folders": "Priečinky", - "Games": "Hry", "Genres": "Žánre", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Spustené prehrávanie audia", "NotificationOptionAudioPlaybackStopped": "Zastavené prehrávanie audia", "NotificationOptionCameraImageUploaded": "Nahraný obrázok z fotoaparátu", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Hra ukončená", "NotificationOptionInstallationFailed": "Chyba inštalácie", "NotificationOptionNewLibraryContent": "Pridaný nový obsah", "NotificationOptionPluginError": "Chyba rozšírenia", diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index 8fe279836..e850257d4 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json index 517d648bb..fb2761a7d 100644 --- a/Emby.Server.Implementations/Localization/Core/sv.json +++ b/Emby.Server.Implementations/Localization/Core/sv.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", "Favorites": "Favoriter", "Folders": "Mappar", - "Games": "Spel", "Genres": "Genrer", "HeaderAlbumArtists": "Albumartister", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", "NotificationOptionAudioPlaybackStopped": "Ljuduppspelning stoppad", "NotificationOptionCameraImageUploaded": "Kamerabild har laddats upp", - "NotificationOptionGamePlayback": "Spel har startats", - "NotificationOptionGamePlaybackStopped": "Spel stoppat", "NotificationOptionInstallationFailed": "Fel vid installation", "NotificationOptionNewLibraryContent": "Nytt innehåll har lagts till", "NotificationOptionPluginError": "Fel uppstod med tillägget", diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 9e86e2125..495f82db6 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 6d877ec17..8910a6bce 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "来自 {0} 的失败登入", "Favorites": "最爱", "Folders": "文件夹", - "Games": "游戏", "Genres": "风格", "HeaderAlbumArtists": "专辑作家", "HeaderCameraUploads": "相机上传", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "音频开始播放", "NotificationOptionAudioPlaybackStopped": "音频播放已停止", "NotificationOptionCameraImageUploaded": "相机图片已上传", - "NotificationOptionGamePlayback": "游戏开始", - "NotificationOptionGamePlaybackStopped": "游戏停止", "NotificationOptionInstallationFailed": "安装失败", "NotificationOptionNewLibraryContent": "已添加新内容", "NotificationOptionPluginError": "插件失败", diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json index cad5700fd..387fc2b92 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-HK.json +++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json @@ -14,7 +14,6 @@ "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "Favorites": "Favorites", "Folders": "Folders", - "Games": "Games", "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", @@ -51,8 +50,6 @@ "NotificationOptionAudioPlayback": "Audio playback started", "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionGamePlayback": "Game playback started", - "NotificationOptionGamePlaybackStopped": "Game playback stopped", "NotificationOptionInstallationFailed": "Installation failure", "NotificationOptionNewLibraryContent": "New content added", "NotificationOptionPluginError": "Plugin failure", diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 7af8cf18c..31217730b 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -4,12 +4,14 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -36,8 +38,7 @@ namespace Emby.Server.Implementations.Localization private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; - private readonly IAssemblyInfo _assemblyInfo; - private readonly ITextLocalizer _textLocalizer; + private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; /// /// Initializes a new instance of the class. @@ -49,67 +50,63 @@ namespace Emby.Server.Implementations.Localization IServerConfigurationManager configurationManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, - ILoggerFactory loggerFactory, - IAssemblyInfo assemblyInfo, - ITextLocalizer textLocalizer) + ILoggerFactory loggerFactory) { _configurationManager = configurationManager; _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _logger = loggerFactory.CreateLogger(nameof(LocalizationManager)); - _assemblyInfo = assemblyInfo; - _textLocalizer = textLocalizer; - - ExtractAll(); } - private void ExtractAll() + public async Task LoadAll() { - var type = GetType(); - var resourcePath = type.Namespace + ".Ratings."; + const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; - var localizationPath = LocalizationPath; + Directory.CreateDirectory(LocalizationPath); - Directory.CreateDirectory(localizationPath); - - var existingFiles = GetRatingsFiles(localizationPath) - .Select(Path.GetFileName) - .ToList(); + var existingFiles = GetRatingsFiles(LocalizationPath).Select(Path.GetFileName); // Extract from the assembly - foreach (var resource in _assemblyInfo - .GetManifestResourceNames(type) - .Where(i => i.StartsWith(resourcePath))) + foreach (var resource in _assembly.GetManifestResourceNames()) { - var filename = "ratings-" + resource.Substring(resourcePath.Length); - - if (!existingFiles.Contains(filename)) + if (!resource.StartsWith(ratingsResource)) { - using (var stream = _assemblyInfo.GetManifestResourceStream(type, resource)) - { - var target = Path.Combine(localizationPath, filename); - _logger.LogInformation("Extracting ratings to {0}", target); + continue; + } - using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - stream.CopyTo(fs); - } + string filename = "ratings-" + resource.Substring(ratingsResource.Length); + + if (existingFiles.Contains(filename)) + { + continue; + } + + using (var stream = _assembly.GetManifestResourceStream(resource)) + { + string target = Path.Combine(LocalizationPath, filename); + _logger.LogInformation("Extracting ratings to {0}", target); + + using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + { + await stream.CopyToAsync(fs); } } } - foreach (var file in GetRatingsFiles(localizationPath)) + foreach (var file in GetRatingsFiles(LocalizationPath)) { - LoadRatings(file); + await LoadRatings(file); } LoadAdditionalRatings(); + + await LoadCultures(); } private void LoadAdditionalRatings() { - LoadRatings("au", new[] { - + LoadRatings("au", new[] + { new ParentalRating("AU-G", 1), new ParentalRating("AU-PG", 5), new ParentalRating("AU-M", 6), @@ -120,8 +117,8 @@ namespace Emby.Server.Implementations.Localization new ParentalRating("AU-RC", 11) }); - LoadRatings("be", new[] { - + LoadRatings("be", new[] + { new ParentalRating("BE-AL", 1), new ParentalRating("BE-MG6", 2), new ParentalRating("BE-6", 3), @@ -130,8 +127,8 @@ namespace Emby.Server.Implementations.Localization new ParentalRating("BE-16", 8) }); - LoadRatings("de", new[] { - + LoadRatings("de", new[] + { new ParentalRating("DE-0", 1), new ParentalRating("FSK-0", 1), new ParentalRating("DE-6", 5), @@ -144,8 +141,8 @@ namespace Emby.Server.Implementations.Localization new ParentalRating("FSK-18", 9) }); - LoadRatings("ru", new[] { - + LoadRatings("ru", new[] + { new ParentalRating("RU-0+", 1), new ParentalRating("RU-6+", 3), new ParentalRating("RU-12+", 7), @@ -159,29 +156,20 @@ namespace Emby.Server.Implementations.Localization _allParentalRatings[country] = ratings.ToDictionary(i => i.Name); } - private List GetRatingsFiles(string directory) - { - return _fileSystem.GetFilePaths(directory, false) - .Where(i => string.Equals(Path.GetExtension(i), ".txt", StringComparison.OrdinalIgnoreCase)) - .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase)) - .ToList(); - } + private IEnumerable GetRatingsFiles(string directory) + => _fileSystem.GetFilePaths(directory, false) + .Where(i => string.Equals(Path.GetExtension(i), ".csv", StringComparison.OrdinalIgnoreCase)) + .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase)); /// /// Gets the localization path. /// /// The localization path. - public string LocalizationPath => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization"); - - public string RemoveDiacritics(string text) - { - return _textLocalizer.RemoveDiacritics(text); - } + public string LocalizationPath + => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization"); public string NormalizeFormKD(string text) - { - return _textLocalizer.NormalizeFormKD(text); - } + => text.Normalize(NormalizationForm.FormKD); private CultureDto[] _cultures; @@ -190,90 +178,88 @@ namespace Emby.Server.Implementations.Localization /// /// IEnumerable{CultureDto}. public CultureDto[] GetCultures() + => _cultures; + + private async Task LoadCultures() { - var result = _cultures; - if (result != null) + List list = new List(); + + const string path = "Emby.Server.Implementations.Localization.iso6392.txt"; + + using (var stream = _assembly.GetManifestResourceStream(path)) + using (var reader = new StreamReader(stream)) { - return result; - } - - var type = GetType(); - var path = type.Namespace + ".iso6392.txt"; - - var list = new List(); - - using (var stream = _assemblyInfo.GetManifestResourceStream(type, path)) - { - using (var reader = new StreamReader(stream)) + while (!reader.EndOfStream) { - while (!reader.EndOfStream) + var line = await reader.ReadLineAsync(); + + if (string.IsNullOrWhiteSpace(line)) { - var line = reader.ReadLine(); + continue; + } - if (!string.IsNullOrWhiteSpace(line)) + var parts = line.Split('|'); + + if (parts.Length == 5) + { + string name = parts[3]; + if (string.IsNullOrWhiteSpace(name)) { - var parts = line.Split('|'); - - if (parts.Length == 5) - { - var threeletterNames = new List { parts[0] }; - if (!string.IsNullOrWhiteSpace(parts[1])) - { - threeletterNames.Add(parts[1]); - } - - list.Add(new CultureDto - { - DisplayName = parts[3], - Name = parts[3], - ThreeLetterISOLanguageNames = threeletterNames.ToArray(), - TwoLetterISOLanguageName = parts[2] - }); - } + continue; } + + string twoCharName = parts[2]; + if (string.IsNullOrWhiteSpace(twoCharName)) + { + continue; + } + + string[] threeletterNames; + if (string.IsNullOrWhiteSpace(parts[1])) + { + threeletterNames = new [] { parts[0] }; + } + else + { + threeletterNames = new [] { parts[0], parts[1] }; + } + + list.Add(new CultureDto + { + DisplayName = name, + Name = name, + ThreeLetterISOLanguageNames = threeletterNames, + TwoLetterISOLanguageName = twoCharName + }); } } } - result = list.Where(i => !string.IsNullOrWhiteSpace(i.Name) && - !string.IsNullOrWhiteSpace(i.DisplayName) && - i.ThreeLetterISOLanguageNames.Length > 0 && - !string.IsNullOrWhiteSpace(i.TwoLetterISOLanguageName)).ToArray(); - - _cultures = result; - - return result; + _cultures = list.ToArray(); } public CultureDto FindLanguageInfo(string language) - { - return GetCultures() - .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || - string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || - i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase) || - string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); - } + => GetCultures() + .FirstOrDefault(i => + string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) + || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) + || i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase) + || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase)); /// /// Gets the countries. /// /// IEnumerable{CountryInfo}. - public CountryInfo[] GetCountries() - { - // ToDo: DeserializeFromStream seems broken in this case - string jsonCountries = "[{\"Name\":\"AF\",\"DisplayName\":\"Afghanistan\",\"TwoLetterISORegionName\":\"AF\",\"ThreeLetterISORegionName\":\"AFG\"},{\"Name\":\"AL\",\"DisplayName\":\"Albania\",\"TwoLetterISORegionName\":\"AL\",\"ThreeLetterISORegionName\":\"ALB\"},{\"Name\":\"DZ\",\"DisplayName\":\"Algeria\",\"TwoLetterISORegionName\":\"DZ\",\"ThreeLetterISORegionName\":\"DZA\"},{\"Name\":\"AR\",\"DisplayName\":\"Argentina\",\"TwoLetterISORegionName\":\"AR\",\"ThreeLetterISORegionName\":\"ARG\"},{\"Name\":\"AM\",\"DisplayName\":\"Armenia\",\"TwoLetterISORegionName\":\"AM\",\"ThreeLetterISORegionName\":\"ARM\"},{\"Name\":\"AU\",\"DisplayName\":\"Australia\",\"TwoLetterISORegionName\":\"AU\",\"ThreeLetterISORegionName\":\"AUS\"},{\"Name\":\"AT\",\"DisplayName\":\"Austria\",\"TwoLetterISORegionName\":\"AT\",\"ThreeLetterISORegionName\":\"AUT\"},{\"Name\":\"AZ\",\"DisplayName\":\"Azerbaijan\",\"TwoLetterISORegionName\":\"AZ\",\"ThreeLetterISORegionName\":\"AZE\"},{\"Name\":\"BH\",\"DisplayName\":\"Bahrain\",\"TwoLetterISORegionName\":\"BH\",\"ThreeLetterISORegionName\":\"BHR\"},{\"Name\":\"BD\",\"DisplayName\":\"Bangladesh\",\"TwoLetterISORegionName\":\"BD\",\"ThreeLetterISORegionName\":\"BGD\"},{\"Name\":\"BY\",\"DisplayName\":\"Belarus\",\"TwoLetterISORegionName\":\"BY\",\"ThreeLetterISORegionName\":\"BLR\"},{\"Name\":\"BE\",\"DisplayName\":\"Belgium\",\"TwoLetterISORegionName\":\"BE\",\"ThreeLetterISORegionName\":\"BEL\"},{\"Name\":\"BZ\",\"DisplayName\":\"Belize\",\"TwoLetterISORegionName\":\"BZ\",\"ThreeLetterISORegionName\":\"BLZ\"},{\"Name\":\"VE\",\"DisplayName\":\"Bolivarian Republic of Venezuela\",\"TwoLetterISORegionName\":\"VE\",\"ThreeLetterISORegionName\":\"VEN\"},{\"Name\":\"BO\",\"DisplayName\":\"Bolivia\",\"TwoLetterISORegionName\":\"BO\",\"ThreeLetterISORegionName\":\"BOL\"},{\"Name\":\"BA\",\"DisplayName\":\"Bosnia and Herzegovina\",\"TwoLetterISORegionName\":\"BA\",\"ThreeLetterISORegionName\":\"BIH\"},{\"Name\":\"BW\",\"DisplayName\":\"Botswana\",\"TwoLetterISORegionName\":\"BW\",\"ThreeLetterISORegionName\":\"BWA\"},{\"Name\":\"BR\",\"DisplayName\":\"Brazil\",\"TwoLetterISORegionName\":\"BR\",\"ThreeLetterISORegionName\":\"BRA\"},{\"Name\":\"BN\",\"DisplayName\":\"Brunei Darussalam\",\"TwoLetterISORegionName\":\"BN\",\"ThreeLetterISORegionName\":\"BRN\"},{\"Name\":\"BG\",\"DisplayName\":\"Bulgaria\",\"TwoLetterISORegionName\":\"BG\",\"ThreeLetterISORegionName\":\"BGR\"},{\"Name\":\"KH\",\"DisplayName\":\"Cambodia\",\"TwoLetterISORegionName\":\"KH\",\"ThreeLetterISORegionName\":\"KHM\"},{\"Name\":\"CM\",\"DisplayName\":\"Cameroon\",\"TwoLetterISORegionName\":\"CM\",\"ThreeLetterISORegionName\":\"CMR\"},{\"Name\":\"CA\",\"DisplayName\":\"Canada\",\"TwoLetterISORegionName\":\"CA\",\"ThreeLetterISORegionName\":\"CAN\"},{\"Name\":\"029\",\"DisplayName\":\"Caribbean\",\"TwoLetterISORegionName\":\"029\",\"ThreeLetterISORegionName\":\"029\"},{\"Name\":\"CL\",\"DisplayName\":\"Chile\",\"TwoLetterISORegionName\":\"CL\",\"ThreeLetterISORegionName\":\"CHL\"},{\"Name\":\"CO\",\"DisplayName\":\"Colombia\",\"TwoLetterISORegionName\":\"CO\",\"ThreeLetterISORegionName\":\"COL\"},{\"Name\":\"CD\",\"DisplayName\":\"Congo [DRC]\",\"TwoLetterISORegionName\":\"CD\",\"ThreeLetterISORegionName\":\"COD\"},{\"Name\":\"CR\",\"DisplayName\":\"Costa Rica\",\"TwoLetterISORegionName\":\"CR\",\"ThreeLetterISORegionName\":\"CRI\"},{\"Name\":\"HR\",\"DisplayName\":\"Croatia\",\"TwoLetterISORegionName\":\"HR\",\"ThreeLetterISORegionName\":\"HRV\"},{\"Name\":\"CZ\",\"DisplayName\":\"Czech Republic\",\"TwoLetterISORegionName\":\"CZ\",\"ThreeLetterISORegionName\":\"CZE\"},{\"Name\":\"DK\",\"DisplayName\":\"Denmark\",\"TwoLetterISORegionName\":\"DK\",\"ThreeLetterISORegionName\":\"DNK\"},{\"Name\":\"DO\",\"DisplayName\":\"Dominican Republic\",\"TwoLetterISORegionName\":\"DO\",\"ThreeLetterISORegionName\":\"DOM\"},{\"Name\":\"EC\",\"DisplayName\":\"Ecuador\",\"TwoLetterISORegionName\":\"EC\",\"ThreeLetterISORegionName\":\"ECU\"},{\"Name\":\"EG\",\"DisplayName\":\"Egypt\",\"TwoLetterISORegionName\":\"EG\",\"ThreeLetterISORegionName\":\"EGY\"},{\"Name\":\"SV\",\"DisplayName\":\"El Salvador\",\"TwoLetterISORegionName\":\"SV\",\"ThreeLetterISORegionName\":\"SLV\"},{\"Name\":\"ER\",\"DisplayName\":\"Eritrea\",\"TwoLetterISORegionName\":\"ER\",\"ThreeLetterISORegionName\":\"ERI\"},{\"Name\":\"EE\",\"DisplayName\":\"Estonia\",\"TwoLetterISORegionName\":\"EE\",\"ThreeLetterISORegionName\":\"EST\"},{\"Name\":\"ET\",\"DisplayName\":\"Ethiopia\",\"TwoLetterISORegionName\":\"ET\",\"ThreeLetterISORegionName\":\"ETH\"},{\"Name\":\"FO\",\"DisplayName\":\"Faroe Islands\",\"TwoLetterISORegionName\":\"FO\",\"ThreeLetterISORegionName\":\"FRO\"},{\"Name\":\"FI\",\"DisplayName\":\"Finland\",\"TwoLetterISORegionName\":\"FI\",\"ThreeLetterISORegionName\":\"FIN\"},{\"Name\":\"FR\",\"DisplayName\":\"France\",\"TwoLetterISORegionName\":\"FR\",\"ThreeLetterISORegionName\":\"FRA\"},{\"Name\":\"GE\",\"DisplayName\":\"Georgia\",\"TwoLetterISORegionName\":\"GE\",\"ThreeLetterISORegionName\":\"GEO\"},{\"Name\":\"DE\",\"DisplayName\":\"Germany\",\"TwoLetterISORegionName\":\"DE\",\"ThreeLetterISORegionName\":\"DEU\"},{\"Name\":\"GR\",\"DisplayName\":\"Greece\",\"TwoLetterISORegionName\":\"GR\",\"ThreeLetterISORegionName\":\"GRC\"},{\"Name\":\"GL\",\"DisplayName\":\"Greenland\",\"TwoLetterISORegionName\":\"GL\",\"ThreeLetterISORegionName\":\"GRL\"},{\"Name\":\"GT\",\"DisplayName\":\"Guatemala\",\"TwoLetterISORegionName\":\"GT\",\"ThreeLetterISORegionName\":\"GTM\"},{\"Name\":\"HT\",\"DisplayName\":\"Haiti\",\"TwoLetterISORegionName\":\"HT\",\"ThreeLetterISORegionName\":\"HTI\"},{\"Name\":\"HN\",\"DisplayName\":\"Honduras\",\"TwoLetterISORegionName\":\"HN\",\"ThreeLetterISORegionName\":\"HND\"},{\"Name\":\"HK\",\"DisplayName\":\"Hong Kong S.A.R.\",\"TwoLetterISORegionName\":\"HK\",\"ThreeLetterISORegionName\":\"HKG\"},{\"Name\":\"HU\",\"DisplayName\":\"Hungary\",\"TwoLetterISORegionName\":\"HU\",\"ThreeLetterISORegionName\":\"HUN\"},{\"Name\":\"IS\",\"DisplayName\":\"Iceland\",\"TwoLetterISORegionName\":\"IS\",\"ThreeLetterISORegionName\":\"ISL\"},{\"Name\":\"IN\",\"DisplayName\":\"India\",\"TwoLetterISORegionName\":\"IN\",\"ThreeLetterISORegionName\":\"IND\"},{\"Name\":\"ID\",\"DisplayName\":\"Indonesia\",\"TwoLetterISORegionName\":\"ID\",\"ThreeLetterISORegionName\":\"IDN\"},{\"Name\":\"IR\",\"DisplayName\":\"Iran\",\"TwoLetterISORegionName\":\"IR\",\"ThreeLetterISORegionName\":\"IRN\"},{\"Name\":\"IQ\",\"DisplayName\":\"Iraq\",\"TwoLetterISORegionName\":\"IQ\",\"ThreeLetterISORegionName\":\"IRQ\"},{\"Name\":\"IE\",\"DisplayName\":\"Ireland\",\"TwoLetterISORegionName\":\"IE\",\"ThreeLetterISORegionName\":\"IRL\"},{\"Name\":\"PK\",\"DisplayName\":\"Islamic Republic of Pakistan\",\"TwoLetterISORegionName\":\"PK\",\"ThreeLetterISORegionName\":\"PAK\"},{\"Name\":\"IL\",\"DisplayName\":\"Israel\",\"TwoLetterISORegionName\":\"IL\",\"ThreeLetterISORegionName\":\"ISR\"},{\"Name\":\"IT\",\"DisplayName\":\"Italy\",\"TwoLetterISORegionName\":\"IT\",\"ThreeLetterISORegionName\":\"ITA\"},{\"Name\":\"CI\",\"DisplayName\":\"Ivory Coast\",\"TwoLetterISORegionName\":\"CI\",\"ThreeLetterISORegionName\":\"CIV\"},{\"Name\":\"JM\",\"DisplayName\":\"Jamaica\",\"TwoLetterISORegionName\":\"JM\",\"ThreeLetterISORegionName\":\"JAM\"},{\"Name\":\"JP\",\"DisplayName\":\"Japan\",\"TwoLetterISORegionName\":\"JP\",\"ThreeLetterISORegionName\":\"JPN\"},{\"Name\":\"JO\",\"DisplayName\":\"Jordan\",\"TwoLetterISORegionName\":\"JO\",\"ThreeLetterISORegionName\":\"JOR\"},{\"Name\":\"KZ\",\"DisplayName\":\"Kazakhstan\",\"TwoLetterISORegionName\":\"KZ\",\"ThreeLetterISORegionName\":\"KAZ\"},{\"Name\":\"KE\",\"DisplayName\":\"Kenya\",\"TwoLetterISORegionName\":\"KE\",\"ThreeLetterISORegionName\":\"KEN\"},{\"Name\":\"KR\",\"DisplayName\":\"Korea\",\"TwoLetterISORegionName\":\"KR\",\"ThreeLetterISORegionName\":\"KOR\"},{\"Name\":\"KW\",\"DisplayName\":\"Kuwait\",\"TwoLetterISORegionName\":\"KW\",\"ThreeLetterISORegionName\":\"KWT\"},{\"Name\":\"KG\",\"DisplayName\":\"Kyrgyzstan\",\"TwoLetterISORegionName\":\"KG\",\"ThreeLetterISORegionName\":\"KGZ\"},{\"Name\":\"LA\",\"DisplayName\":\"Lao P.D.R.\",\"TwoLetterISORegionName\":\"LA\",\"ThreeLetterISORegionName\":\"LAO\"},{\"Name\":\"419\",\"DisplayName\":\"Latin America\",\"TwoLetterISORegionName\":\"419\",\"ThreeLetterISORegionName\":\"419\"},{\"Name\":\"LV\",\"DisplayName\":\"Latvia\",\"TwoLetterISORegionName\":\"LV\",\"ThreeLetterISORegionName\":\"LVA\"},{\"Name\":\"LB\",\"DisplayName\":\"Lebanon\",\"TwoLetterISORegionName\":\"LB\",\"ThreeLetterISORegionName\":\"LBN\"},{\"Name\":\"LY\",\"DisplayName\":\"Libya\",\"TwoLetterISORegionName\":\"LY\",\"ThreeLetterISORegionName\":\"LBY\"},{\"Name\":\"LI\",\"DisplayName\":\"Liechtenstein\",\"TwoLetterISORegionName\":\"LI\",\"ThreeLetterISORegionName\":\"LIE\"},{\"Name\":\"LT\",\"DisplayName\":\"Lithuania\",\"TwoLetterISORegionName\":\"LT\",\"ThreeLetterISORegionName\":\"LTU\"},{\"Name\":\"LU\",\"DisplayName\":\"Luxembourg\",\"TwoLetterISORegionName\":\"LU\",\"ThreeLetterISORegionName\":\"LUX\"},{\"Name\":\"MO\",\"DisplayName\":\"Macao S.A.R.\",\"TwoLetterISORegionName\":\"MO\",\"ThreeLetterISORegionName\":\"MAC\"},{\"Name\":\"MK\",\"DisplayName\":\"Macedonia (FYROM)\",\"TwoLetterISORegionName\":\"MK\",\"ThreeLetterISORegionName\":\"MKD\"},{\"Name\":\"MY\",\"DisplayName\":\"Malaysia\",\"TwoLetterISORegionName\":\"MY\",\"ThreeLetterISORegionName\":\"MYS\"},{\"Name\":\"MV\",\"DisplayName\":\"Maldives\",\"TwoLetterISORegionName\":\"MV\",\"ThreeLetterISORegionName\":\"MDV\"},{\"Name\":\"ML\",\"DisplayName\":\"Mali\",\"TwoLetterISORegionName\":\"ML\",\"ThreeLetterISORegionName\":\"MLI\"},{\"Name\":\"MT\",\"DisplayName\":\"Malta\",\"TwoLetterISORegionName\":\"MT\",\"ThreeLetterISORegionName\":\"MLT\"},{\"Name\":\"MX\",\"DisplayName\":\"Mexico\",\"TwoLetterISORegionName\":\"MX\",\"ThreeLetterISORegionName\":\"MEX\"},{\"Name\":\"MN\",\"DisplayName\":\"Mongolia\",\"TwoLetterISORegionName\":\"MN\",\"ThreeLetterISORegionName\":\"MNG\"},{\"Name\":\"ME\",\"DisplayName\":\"Montenegro\",\"TwoLetterISORegionName\":\"ME\",\"ThreeLetterISORegionName\":\"MNE\"},{\"Name\":\"MA\",\"DisplayName\":\"Morocco\",\"TwoLetterISORegionName\":\"MA\",\"ThreeLetterISORegionName\":\"MAR\"},{\"Name\":\"NP\",\"DisplayName\":\"Nepal\",\"TwoLetterISORegionName\":\"NP\",\"ThreeLetterISORegionName\":\"NPL\"},{\"Name\":\"NL\",\"DisplayName\":\"Netherlands\",\"TwoLetterISORegionName\":\"NL\",\"ThreeLetterISORegionName\":\"NLD\"},{\"Name\":\"NZ\",\"DisplayName\":\"New Zealand\",\"TwoLetterISORegionName\":\"NZ\",\"ThreeLetterISORegionName\":\"NZL\"},{\"Name\":\"NI\",\"DisplayName\":\"Nicaragua\",\"TwoLetterISORegionName\":\"NI\",\"ThreeLetterISORegionName\":\"NIC\"},{\"Name\":\"NG\",\"DisplayName\":\"Nigeria\",\"TwoLetterISORegionName\":\"NG\",\"ThreeLetterISORegionName\":\"NGA\"},{\"Name\":\"NO\",\"DisplayName\":\"Norway\",\"TwoLetterISORegionName\":\"NO\",\"ThreeLetterISORegionName\":\"NOR\"},{\"Name\":\"OM\",\"DisplayName\":\"Oman\",\"TwoLetterISORegionName\":\"OM\",\"ThreeLetterISORegionName\":\"OMN\"},{\"Name\":\"PA\",\"DisplayName\":\"Panama\",\"TwoLetterISORegionName\":\"PA\",\"ThreeLetterISORegionName\":\"PAN\"},{\"Name\":\"PY\",\"DisplayName\":\"Paraguay\",\"TwoLetterISORegionName\":\"PY\",\"ThreeLetterISORegionName\":\"PRY\"},{\"Name\":\"CN\",\"DisplayName\":\"People's Republic of China\",\"TwoLetterISORegionName\":\"CN\",\"ThreeLetterISORegionName\":\"CHN\"},{\"Name\":\"PE\",\"DisplayName\":\"Peru\",\"TwoLetterISORegionName\":\"PE\",\"ThreeLetterISORegionName\":\"PER\"},{\"Name\":\"PH\",\"DisplayName\":\"Philippines\",\"TwoLetterISORegionName\":\"PH\",\"ThreeLetterISORegionName\":\"PHL\"},{\"Name\":\"PL\",\"DisplayName\":\"Poland\",\"TwoLetterISORegionName\":\"PL\",\"ThreeLetterISORegionName\":\"POL\"},{\"Name\":\"PT\",\"DisplayName\":\"Portugal\",\"TwoLetterISORegionName\":\"PT\",\"ThreeLetterISORegionName\":\"PRT\"},{\"Name\":\"MC\",\"DisplayName\":\"Principality of Monaco\",\"TwoLetterISORegionName\":\"MC\",\"ThreeLetterISORegionName\":\"MCO\"},{\"Name\":\"PR\",\"DisplayName\":\"Puerto Rico\",\"TwoLetterISORegionName\":\"PR\",\"ThreeLetterISORegionName\":\"PRI\"},{\"Name\":\"QA\",\"DisplayName\":\"Qatar\",\"TwoLetterISORegionName\":\"QA\",\"ThreeLetterISORegionName\":\"QAT\"},{\"Name\":\"MD\",\"DisplayName\":\"Republica Moldova\",\"TwoLetterISORegionName\":\"MD\",\"ThreeLetterISORegionName\":\"MDA\"},{\"Name\":\"RE\",\"DisplayName\":\"Réunion\",\"TwoLetterISORegionName\":\"RE\",\"ThreeLetterISORegionName\":\"REU\"},{\"Name\":\"RO\",\"DisplayName\":\"Romania\",\"TwoLetterISORegionName\":\"RO\",\"ThreeLetterISORegionName\":\"ROU\"},{\"Name\":\"RU\",\"DisplayName\":\"Russia\",\"TwoLetterISORegionName\":\"RU\",\"ThreeLetterISORegionName\":\"RUS\"},{\"Name\":\"RW\",\"DisplayName\":\"Rwanda\",\"TwoLetterISORegionName\":\"RW\",\"ThreeLetterISORegionName\":\"RWA\"},{\"Name\":\"SA\",\"DisplayName\":\"Saudi Arabia\",\"TwoLetterISORegionName\":\"SA\",\"ThreeLetterISORegionName\":\"SAU\"},{\"Name\":\"SN\",\"DisplayName\":\"Senegal\",\"TwoLetterISORegionName\":\"SN\",\"ThreeLetterISORegionName\":\"SEN\"},{\"Name\":\"RS\",\"DisplayName\":\"Serbia\",\"TwoLetterISORegionName\":\"RS\",\"ThreeLetterISORegionName\":\"SRB\"},{\"Name\":\"CS\",\"DisplayName\":\"Serbia and Montenegro (Former)\",\"TwoLetterISORegionName\":\"CS\",\"ThreeLetterISORegionName\":\"SCG\"},{\"Name\":\"SG\",\"DisplayName\":\"Singapore\",\"TwoLetterISORegionName\":\"SG\",\"ThreeLetterISORegionName\":\"SGP\"},{\"Name\":\"SK\",\"DisplayName\":\"Slovakia\",\"TwoLetterISORegionName\":\"SK\",\"ThreeLetterISORegionName\":\"SVK\"},{\"Name\":\"SI\",\"DisplayName\":\"Slovenia\",\"TwoLetterISORegionName\":\"SI\",\"ThreeLetterISORegionName\":\"SVN\"},{\"Name\":\"SO\",\"DisplayName\":\"Soomaaliya\",\"TwoLetterISORegionName\":\"SO\",\"ThreeLetterISORegionName\":\"SOM\"},{\"Name\":\"ZA\",\"DisplayName\":\"South Africa\",\"TwoLetterISORegionName\":\"ZA\",\"ThreeLetterISORegionName\":\"ZAF\"},{\"Name\":\"ES\",\"DisplayName\":\"Spain\",\"TwoLetterISORegionName\":\"ES\",\"ThreeLetterISORegionName\":\"ESP\"},{\"Name\":\"LK\",\"DisplayName\":\"Sri Lanka\",\"TwoLetterISORegionName\":\"LK\",\"ThreeLetterISORegionName\":\"LKA\"},{\"Name\":\"SE\",\"DisplayName\":\"Sweden\",\"TwoLetterISORegionName\":\"SE\",\"ThreeLetterISORegionName\":\"SWE\"},{\"Name\":\"CH\",\"DisplayName\":\"Switzerland\",\"TwoLetterISORegionName\":\"CH\",\"ThreeLetterISORegionName\":\"CHE\"},{\"Name\":\"SY\",\"DisplayName\":\"Syria\",\"TwoLetterISORegionName\":\"SY\",\"ThreeLetterISORegionName\":\"SYR\"},{\"Name\":\"TW\",\"DisplayName\":\"Taiwan\",\"TwoLetterISORegionName\":\"TW\",\"ThreeLetterISORegionName\":\"TWN\"},{\"Name\":\"TJ\",\"DisplayName\":\"Tajikistan\",\"TwoLetterISORegionName\":\"TJ\",\"ThreeLetterISORegionName\":\"TAJ\"},{\"Name\":\"TH\",\"DisplayName\":\"Thailand\",\"TwoLetterISORegionName\":\"TH\",\"ThreeLetterISORegionName\":\"THA\"},{\"Name\":\"TT\",\"DisplayName\":\"Trinidad and Tobago\",\"TwoLetterISORegionName\":\"TT\",\"ThreeLetterISORegionName\":\"TTO\"},{\"Name\":\"TN\",\"DisplayName\":\"Tunisia\",\"TwoLetterISORegionName\":\"TN\",\"ThreeLetterISORegionName\":\"TUN\"},{\"Name\":\"TR\",\"DisplayName\":\"Turkey\",\"TwoLetterISORegionName\":\"TR\",\"ThreeLetterISORegionName\":\"TUR\"},{\"Name\":\"TM\",\"DisplayName\":\"Turkmenistan\",\"TwoLetterISORegionName\":\"TM\",\"ThreeLetterISORegionName\":\"TKM\"},{\"Name\":\"AE\",\"DisplayName\":\"U.A.E.\",\"TwoLetterISORegionName\":\"AE\",\"ThreeLetterISORegionName\":\"ARE\"},{\"Name\":\"UA\",\"DisplayName\":\"Ukraine\",\"TwoLetterISORegionName\":\"UA\",\"ThreeLetterISORegionName\":\"UKR\"},{\"Name\":\"GB\",\"DisplayName\":\"United Kingdom\",\"TwoLetterISORegionName\":\"GB\",\"ThreeLetterISORegionName\":\"GBR\"},{\"Name\":\"US\",\"DisplayName\":\"United States\",\"TwoLetterISORegionName\":\"US\",\"ThreeLetterISORegionName\":\"USA\"},{\"Name\":\"UY\",\"DisplayName\":\"Uruguay\",\"TwoLetterISORegionName\":\"UY\",\"ThreeLetterISORegionName\":\"URY\"},{\"Name\":\"UZ\",\"DisplayName\":\"Uzbekistan\",\"TwoLetterISORegionName\":\"UZ\",\"ThreeLetterISORegionName\":\"UZB\"},{\"Name\":\"VN\",\"DisplayName\":\"Vietnam\",\"TwoLetterISORegionName\":\"VN\",\"ThreeLetterISORegionName\":\"VNM\"},{\"Name\":\"YE\",\"DisplayName\":\"Yemen\",\"TwoLetterISORegionName\":\"YE\",\"ThreeLetterISORegionName\":\"YEM\"},{\"Name\":\"ZW\",\"DisplayName\":\"Zimbabwe\",\"TwoLetterISORegionName\":\"ZW\",\"ThreeLetterISORegionName\":\"ZWE\"}]"; - - return _jsonSerializer.DeserializeFromString(jsonCountries); - } + public Task GetCountries() + => _jsonSerializer.DeserializeFromStreamAsync( + _assembly.GetManifestResourceStream("Emby.Server.Implementations.Localization.countries.json")); /// /// Gets the parental ratings. /// /// IEnumerable{ParentalRating}. - public ParentalRating[] GetParentalRatings() - { - return GetParentalRatingsDictionary().Values.ToArray(); - } + public IEnumerable GetParentalRatings() + => GetParentalRatingsDictionary().Values; /// /// Gets the parental ratings dictionary. @@ -288,14 +274,7 @@ namespace Emby.Server.Implementations.Localization countryCode = "us"; } - var ratings = GetRatings(countryCode); - - if (ratings == null) - { - ratings = GetRatings("us"); - } - - return ratings; + return GetRatings(countryCode) ?? GetRatings("us"); } /// @@ -314,37 +293,43 @@ namespace Emby.Server.Implementations.Localization /// /// The file. /// Dictionary{System.StringParentalRating}. - private void LoadRatings(string file) + private async Task LoadRatings(string file) { - var dict = File.ReadAllLines(file).Select(i => + Dictionary dict + = new Dictionary(StringComparer.OrdinalIgnoreCase); + + using (var str = File.OpenRead(file)) + using (var reader = new StreamReader(str)) { - if (!string.IsNullOrWhiteSpace(i)) + string line; + while ((line = await reader.ReadLineAsync()) != null) { - var parts = i.Split(','); - - if (parts.Length == 2) + if (string.IsNullOrWhiteSpace(line)) { - if (int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) - { - return new ParentalRating { Name = parts[0], Value = value }; - } + continue; } + + string[] parts = line.Split(','); + if (parts.Length == 2 + && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value)) + { + dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value })); + } +#if DEBUG + else + { + _logger.LogWarning("Misformed line in {Path}", file); + } +#endif } + } - return null; - - }) - .Where(i => i != null) - .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); - - var countryCode = Path.GetFileNameWithoutExtension(file) - .Split('-') - .Last(); + var countryCode = Path.GetFileNameWithoutExtension(file).Split('-')[1]; _allParentalRatings[countryCode] = dict; } - private readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; + private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; /// /// Gets the rating level. @@ -435,7 +420,7 @@ namespace Emby.Server.Implementations.Localization return phrase; } - const string DefaultCulture = "en-US"; + private const string DefaultCulture = "en-US"; private readonly ConcurrentDictionary> _dictionaries = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); @@ -450,10 +435,11 @@ namespace Emby.Server.Implementations.Localization const string prefix = "Core"; var key = prefix + culture; - return _dictionaries.GetOrAdd(key, k => GetDictionary(prefix, culture, DefaultCulture + ".json")); + return _dictionaries.GetOrAdd(key, + f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult()); } - private Dictionary GetDictionary(string prefix, string culture, string baseFilename) + private async Task> GetDictionary(string prefix, string culture, string baseFilename) { if (string.IsNullOrEmpty(culture)) { @@ -464,25 +450,30 @@ namespace Emby.Server.Implementations.Localization var namespaceName = GetType().Namespace + "." + prefix; - CopyInto(dictionary, namespaceName + "." + baseFilename); - CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)); + await CopyInto(dictionary, namespaceName + "." + baseFilename); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)); return dictionary; } - private void CopyInto(IDictionary dictionary, string resourcePath) + private async Task CopyInto(IDictionary dictionary, string resourcePath) { - using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), resourcePath)) + using (var stream = _assembly.GetManifestResourceStream(resourcePath)) { + // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain if (stream != null) { - var dict = _jsonSerializer.DeserializeFromStream>(stream); + var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream); foreach (var key in dict.Keys) { dictionary[key] = dict[key]; } } + else + { + _logger.LogError("Missing translation/culture resource: {ResourcePath}", resourcePath); + } } } @@ -492,11 +483,11 @@ namespace Emby.Server.Implementations.Localization if (parts.Length == 2) { - culture = parts[0].ToLower() + "-" + parts[1].ToUpper(); + culture = parts[0].ToLowerInvariant() + "-" + parts[1].ToUpperInvariant(); } else { - culture = culture.ToLower(); + culture = culture.ToLowerInvariant(); } return culture + ".json"; @@ -506,29 +497,23 @@ namespace Emby.Server.Implementations.Localization => new LocalizationOption[] { new LocalizationOption("Arabic", "ar"), - new LocalizationOption("Belarusian (Belarus)", "be-BY"), new LocalizationOption("Bulgarian (Bulgaria)", "bg-BG"), new LocalizationOption("Catalan", "ca"), new LocalizationOption("Chinese Simplified", "zh-CN"), new LocalizationOption("Chinese Traditional", "zh-TW"), - new LocalizationOption("Chinese Traditional (Hong Kong)", "zh-HK"), new LocalizationOption("Croatian", "hr"), new LocalizationOption("Czech", "cs"), new LocalizationOption("Danish", "da"), new LocalizationOption("Dutch", "nl"), new LocalizationOption("English (United Kingdom)", "en-GB"), new LocalizationOption("English (United States)", "en-US"), - new LocalizationOption("Finnish", "fi"), new LocalizationOption("French", "fr"), new LocalizationOption("French (Canada)", "fr-CA"), new LocalizationOption("German", "de"), new LocalizationOption("Greek", "el"), new LocalizationOption("Hebrew", "he"), - new LocalizationOption("Hindi (India)", "hi-IN"), new LocalizationOption("Hungarian", "hu"), - new LocalizationOption("Indonesian", "id"), new LocalizationOption("Italian", "it"), - new LocalizationOption("Japanese", "ja"), new LocalizationOption("Kazakh", "kk"), new LocalizationOption("Korean", "ko"), new LocalizationOption("Lithuanian", "lt-LT"), @@ -538,25 +523,15 @@ namespace Emby.Server.Implementations.Localization new LocalizationOption("Polish", "pl"), new LocalizationOption("Portuguese (Brazil)", "pt-BR"), new LocalizationOption("Portuguese (Portugal)", "pt-PT"), - new LocalizationOption("Romanian", "ro"), new LocalizationOption("Russian", "ru"), new LocalizationOption("Slovak", "sk"), new LocalizationOption("Slovenian (Slovenia)", "sl-SI"), new LocalizationOption("Spanish", "es"), - new LocalizationOption("Spanish (Latin America)", "es-419"), + new LocalizationOption("Spanish (Argentina)", "es-AR"), new LocalizationOption("Spanish (Mexico)", "es-MX"), new LocalizationOption("Swedish", "sv"), new LocalizationOption("Swiss German", "gsw"), - new LocalizationOption("Turkish", "tr"), - new LocalizationOption("Ukrainian", "uk"), - new LocalizationOption("Vietnamese", "vi") + new LocalizationOption("Turkish", "tr") }; } - - public interface ITextLocalizer - { - string RemoveDiacritics(string text); - - string NormalizeFormKD(string text); - } } diff --git a/Emby.Server.Implementations/Localization/Ratings/br.txt b/Emby.Server.Implementations/Localization/Ratings/br.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/br.txt rename to Emby.Server.Implementations/Localization/Ratings/br.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/ca.txt b/Emby.Server.Implementations/Localization/Ratings/ca.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/ca.txt rename to Emby.Server.Implementations/Localization/Ratings/ca.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/co.txt b/Emby.Server.Implementations/Localization/Ratings/co.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/co.txt rename to Emby.Server.Implementations/Localization/Ratings/co.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/dk.txt b/Emby.Server.Implementations/Localization/Ratings/dk.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/dk.txt rename to Emby.Server.Implementations/Localization/Ratings/dk.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/es.txt b/Emby.Server.Implementations/Localization/Ratings/es.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/es.txt rename to Emby.Server.Implementations/Localization/Ratings/es.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/fr.txt b/Emby.Server.Implementations/Localization/Ratings/fr.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/fr.txt rename to Emby.Server.Implementations/Localization/Ratings/fr.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/gb.txt b/Emby.Server.Implementations/Localization/Ratings/gb.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/gb.txt rename to Emby.Server.Implementations/Localization/Ratings/gb.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/ie.txt b/Emby.Server.Implementations/Localization/Ratings/ie.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/ie.txt rename to Emby.Server.Implementations/Localization/Ratings/ie.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/jp.txt b/Emby.Server.Implementations/Localization/Ratings/jp.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/jp.txt rename to Emby.Server.Implementations/Localization/Ratings/jp.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.txt b/Emby.Server.Implementations/Localization/Ratings/kz.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/kz.txt rename to Emby.Server.Implementations/Localization/Ratings/kz.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/mx.txt b/Emby.Server.Implementations/Localization/Ratings/mx.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/mx.txt rename to Emby.Server.Implementations/Localization/Ratings/mx.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/nl.txt b/Emby.Server.Implementations/Localization/Ratings/nl.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/nl.txt rename to Emby.Server.Implementations/Localization/Ratings/nl.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/nz.txt b/Emby.Server.Implementations/Localization/Ratings/nz.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/nz.txt rename to Emby.Server.Implementations/Localization/Ratings/nz.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/ro.txt b/Emby.Server.Implementations/Localization/Ratings/ro.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/ro.txt rename to Emby.Server.Implementations/Localization/Ratings/ro.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/uk.txt b/Emby.Server.Implementations/Localization/Ratings/uk.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/uk.txt rename to Emby.Server.Implementations/Localization/Ratings/uk.csv diff --git a/Emby.Server.Implementations/Localization/Ratings/us.txt b/Emby.Server.Implementations/Localization/Ratings/us.csv similarity index 100% rename from Emby.Server.Implementations/Localization/Ratings/us.txt rename to Emby.Server.Implementations/Localization/Ratings/us.csv diff --git a/Emby.Server.Implementations/Localization/TextLocalizer.cs b/Emby.Server.Implementations/Localization/TextLocalizer.cs deleted file mode 100644 index 96591e5e6..000000000 --- a/Emby.Server.Implementations/Localization/TextLocalizer.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Emby.Server.Implementations.Localization -{ - public class TextLocalizer : ITextLocalizer - { - public string RemoveDiacritics(string text) - { - if (text == null) - { - throw new ArgumentNullException(nameof(text)); - } - - var chars = Normalize(text, NormalizationForm.FormD) - .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark); - - return Normalize(string.Concat(chars), NormalizationForm.FormC); - } - - private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true) - { - if (stripStringOnFailure) - { - try - { - return text.Normalize(form); - } - catch (ArgumentException) - { - // will throw if input contains invalid unicode chars - // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ - text = StripInvalidUnicodeCharacters(text); - return Normalize(text, form, false); - } - } - - try - { - return text.Normalize(form); - } - catch (ArgumentException) - { - // if it still fails, return the original text - return text; - } - } - - private static string StripInvalidUnicodeCharacters(string str) - { - var invalidCharactersRegex = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((? /// Extension methods to convert /// instances to hexadecimal, octal, and binary strings. diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs index c5853135c..a0c5f73af 100644 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPAddressCollection.cs @@ -1,8 +1,10 @@ +using System; using System.Collections; using System.Collections.Generic; +using System.Net; using System.Numerics; -namespace System.Net +namespace Emby.Server.Implementations.Networking.IPNetwork { public class IPAddressCollection : IEnumerable, IEnumerator { @@ -29,7 +31,7 @@ namespace System.Net { throw new ArgumentOutOfRangeException(nameof(i)); } - byte width = this._ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetwork ? (byte)32 : (byte)128; + byte width = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? (byte)32 : (byte)128; var ipn = this._ipnetwork.Subnet(width); return ipn[i].Network; } diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs index 16f39daf7..d6de61c0c 100644 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPNetwork.cs @@ -1,10 +1,12 @@ +using System; using System.Collections.Generic; using System.IO; +using System.Net; using System.Net.Sockets; using System.Numerics; using System.Text.RegularExpressions; -namespace System.Net +namespace Emby.Server.Implementations.Networking.IPNetwork { /// /// IP Network utility class. @@ -60,7 +62,7 @@ namespace System.Net get { - int width = this._family == Sockets.AddressFamily.InterNetwork ? 4 : 16; + int width = this._family == System.Net.Sockets.AddressFamily.InterNetwork ? 4 : 16; var uintBroadcast = this._network + this._netmask.PositiveReverse(width); return uintBroadcast; } @@ -73,7 +75,7 @@ namespace System.Net { get { - if (this._family == Sockets.AddressFamily.InterNetworkV6) + if (this._family == System.Net.Sockets.AddressFamily.InterNetworkV6) { return null; } @@ -88,7 +90,7 @@ namespace System.Net { get { - var fisrt = this._family == Sockets.AddressFamily.InterNetworkV6 + var fisrt = this._family == System.Net.Sockets.AddressFamily.InterNetworkV6 ? this._network : (this.Usable <= 0) ? this._network : this._network + 1; return IPNetwork.ToIPAddress(fisrt, this._family); @@ -102,7 +104,7 @@ namespace System.Net { get { - var last = this._family == Sockets.AddressFamily.InterNetworkV6 + var last = this._family == System.Net.Sockets.AddressFamily.InterNetworkV6 ? this._broadcast : (this.Usable <= 0) ? this._network : this._broadcast - 1; return IPNetwork.ToIPAddress(last, this._family); @@ -117,7 +119,7 @@ namespace System.Net get { - if (this._family == Sockets.AddressFamily.InterNetworkV6) + if (this._family == System.Net.Sockets.AddressFamily.InterNetworkV6) { return this.Total; } @@ -136,7 +138,7 @@ namespace System.Net get { - int max = this._family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + int max = this._family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; var count = BigInteger.Pow(2, (max - _cidr)); return count; } @@ -161,7 +163,7 @@ namespace System.Net IPNetwork(BigInteger ipaddress, AddressFamily family, byte cidr) { - int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + int maxCidr = family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; if (cidr > maxCidr) { throw new ArgumentOutOfRangeException(nameof(cidr)); @@ -930,7 +932,7 @@ namespace System.Net /// return; /// } - int maxCidr = family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + int maxCidr = family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; if (cidr > maxCidr) { if (tryParse == false) @@ -1303,7 +1305,7 @@ namespace System.Net return; } - int maxCidr = network._family == Sockets.AddressFamily.InterNetwork ? 32 : 128; + int maxCidr = network._family == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; if (cidr > maxCidr) { if (trySubnet == false) diff --git a/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs b/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs index 7d3106624..4cda421e5 100644 --- a/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs +++ b/Emby.Server.Implementations/Networking/IPNetwork/IPNetworkCollection.cs @@ -1,8 +1,9 @@ +using System; using System.Collections; using System.Collections.Generic; using System.Numerics; -namespace System.Net +namespace Emby.Server.Implementations.Networking.IPNetwork { public class IPNetworkCollection : IEnumerable, IEnumerator { @@ -25,7 +26,7 @@ namespace System.Net IPNetworkCollection(IPNetwork ipnetwork, byte cidrSubnet) { - int maxCidr = ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetwork ? 32 : 128; + int maxCidr = ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork ? 32 : 128; if (cidrSubnet > maxCidr) { throw new ArgumentOutOfRangeException(nameof(cidrSubnet)); @@ -61,7 +62,7 @@ namespace System.Net throw new ArgumentOutOfRangeException(nameof(i)); } - var last = this._ipnetwork.AddressFamily == Sockets.AddressFamily.InterNetworkV6 + var last = this._ipnetwork.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 ? this._lastUsable : this._broadcast; var increment = (last - this._network) / this.Count; var uintNetwork = this._network + ((increment + 1) * i); diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index f4b9f84dc..60cc9b88e 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -111,7 +111,8 @@ namespace Emby.Server.Implementations.Networking .OrderBy(i => i.AddressFamily == AddressFamily.InterNetwork ? 0 : 1) .ThenBy(i => listClone.IndexOf(i)) .Where(FilterIpAddress) - .DistinctBy(i => i.ToString()) + .GroupBy(i => i.ToString()) + .Select(x => x.First()) .ToList(); } @@ -280,7 +281,7 @@ namespace Emby.Server.Implementations.Networking if (normalizedSubnet.IndexOf('/') != -1) { - var ipnetwork = IPNetwork.Parse(normalizedSubnet); + var ipnetwork = IPNetwork.IPNetwork.Parse(normalizedSubnet); if (ipnetwork.Contains(address)) { return true; @@ -429,7 +430,8 @@ namespace Emby.Server.Implementations.Networking return new List(); } - }).DistinctBy(i => i.ToString()) + }).GroupBy(i => i.ToString()) + .Select(x => x.First()) .ToList(); } diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index 223153164..8a7c1492d 100644 --- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs +++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs @@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; @@ -64,7 +63,8 @@ namespace Emby.Server.Implementations.Playlists }) .Where(i => i != null) .OrderBy(i => Guid.NewGuid()) - .DistinctBy(i => i.Id) + .GroupBy(x => x.Id) + .Select(x => x.First()) .ToList(); } } diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 2b648b04b..08bb39578 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -10,7 +10,6 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index b8479fd26..595c3037d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Events; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -74,38 +72,6 @@ namespace Emby.Server.Implementations.ScheduledTasks ScheduledTasks = new IScheduledTaskWorker[] { }; } - private void RunStartupTasks() - { - var path = Path.Combine(ApplicationPaths.CachePath, "startuptasks.txt"); - - // ToDo: Fix this shit - if (!File.Exists(path)) - return; - - List lines; - - try - { - lines = File.ReadAllLines(path).Where(i => !string.IsNullOrWhiteSpace(i)).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); - - foreach (var key in lines) - { - var task = ScheduledTasks.FirstOrDefault(i => string.Equals(i.ScheduledTask.Key, key, StringComparison.OrdinalIgnoreCase)); - - if (task != null) - { - QueueScheduledTask(task, new TaskOptions()); - } - } - - _fileSystem.DeleteFile(path); - } - catch - { - return; - } - } - /// /// Cancels if running and queue. /// @@ -247,14 +213,9 @@ namespace Emby.Server.Implementations.ScheduledTasks /// The tasks. public void AddTasks(IEnumerable tasks) { - var myTasks = ScheduledTasks.ToList(); + var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)); - var list = tasks.ToList(); - myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem))); - - ScheduledTasks = myTasks.ToArray(); - - RunStartupTasks(); + ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } /// diff --git a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs similarity index 94% rename from Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs rename to Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 5373b4392..81fdb96d2 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -68,8 +68,6 @@ namespace Emby.Server.Implementations.ScheduledTasks }; } - public string Key => "RefreshChapterImages"; - /// /// Returns the task to be executed /// @@ -161,22 +159,18 @@ namespace Emby.Server.Implementations.ScheduledTasks } } - /// - /// Gets the name of the task - /// - /// The name. public string Name => "Chapter image extraction"; - /// - /// Gets the description. - /// - /// The description. public string Description => "Creates thumbnails for videos that have chapters."; - /// - /// Gets the category. - /// - /// The category. public string Category => "Library"; + + public string Key => "RefreshChapterImages"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 52077b242..6ec83b5c0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -158,31 +158,15 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks } } - /// - /// Gets the name of the task - /// - /// The name. public string Name => "Cache file cleanup"; + public string Description => "Deletes cache files no longer needed by the system"; + + public string Category => "Maintenance"; + public string Key => "DeleteCacheFiles"; - /// - /// Gets the description. - /// - /// The description. - public string Description => "Deletes cache files no longer needed by the system"; - - /// - /// Gets the category. - /// - /// The category. - public string Category => "Maintenance"; - - /// - /// Gets a value indicating whether this instance is hidden. - /// - /// true if this instance is hidden; otherwise, false. - public bool IsHidden => true; + public bool IsHidden => false; public bool IsEnabled => true; diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index a57fe4945..e468c301a 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -81,31 +81,15 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } - public string Key => "CleanLogFiles"; - - /// - /// Gets the name of the task - /// - /// The name. public string Name => "Log file cleanup"; - /// - /// Gets the description. - /// - /// The description. public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays); - /// - /// Gets the category. - /// - /// The category. public string Category => "Maintenance"; - /// - /// Gets a value indicating whether this instance is hidden. - /// - /// true if this instance is hidden; otherwise, false. - public bool IsHidden => true; + public string Key => "CleanLogFiles"; + + public bool IsHidden => false; public bool IsEnabled => true; diff --git a/Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs similarity index 86% rename from Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs rename to Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index 68031170f..d70799c47 100644 --- a/Emby.Server.Implementations/ScheduledTasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -47,8 +47,6 @@ namespace Emby.Server.Implementations.ScheduledTasks }; } - public string Key => "RefreshPeople"; - /// /// Returns the task to be executed /// @@ -60,22 +58,18 @@ namespace Emby.Server.Implementations.ScheduledTasks return _libraryManager.ValidatePeople(cancellationToken, progress); } - /// - /// Gets the name of the task - /// - /// The name. public string Name => "Refresh people"; - /// - /// Gets the description. - /// - /// The description. public string Description => "Updates metadata for actors and directors in your media library."; - /// - /// Gets the category. - /// - /// The category. public string Category => "Library"; + + public string Key => "RefreshPeople"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs new file mode 100644 index 000000000..c6431c311 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -0,0 +1,119 @@ +using MediaBrowser.Common; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Net; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Progress; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks +{ + /// + /// Plugin Update Task + /// + public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask + { + /// + /// The _logger + /// + private readonly ILogger _logger; + + private readonly IInstallationManager _installationManager; + + private readonly IApplicationHost _appHost; + + public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost) + { + _logger = logger; + _installationManager = installationManager; + _appHost = appHost; + } + + /// + /// Creates the triggers that define when the task will run + /// + /// IEnumerable{BaseTaskTrigger}. + public IEnumerable GetDefaultTriggers() + { + return new[] { + + // At startup + new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup}, + + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + }; + } + + /// + /// Update installed plugins + /// + /// The cancellation token. + /// The progress. + /// Task. + public async Task Execute(CancellationToken cancellationToken, IProgress progress) + { + progress.Report(0); + + var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList(); + + progress.Report(10); + + var numComplete = 0; + + foreach (var package in packagesToInstall) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await _installationManager.InstallPackage(package, true, new SimpleProgress(), cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // InstallPackage has it's own inner cancellation token, so only throw this if it's ours + if (cancellationToken.IsCancellationRequested) + { + throw; + } + } + catch (HttpException ex) + { + _logger.LogError(ex, "Error downloading {0}", package.name); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error updating {0}", package.name); + } + + // Update progress + lock (progress) + { + numComplete++; + progress.Report(90.0 * numComplete / packagesToInstall.Count + 10); + } + } + + progress.Report(100); + } + + public string Name => "Check for plugin updates"; + + public string Description => "Downloads and installs updates for plugins that are configured to update automatically."; + + public string Category => "Application"; + + public string Key => "PluginUpdates"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs similarity index 87% rename from Emby.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs rename to Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index f6fa64d13..1a3d85ad7 100644 --- a/Emby.Server.Implementations/ScheduledTasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -58,24 +58,18 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } - /// - /// Gets the name. - /// - /// The name. public string Name => "Scan media library"; - /// - /// Gets the description. - /// - /// The description. public string Description => "Scans your media library and refreshes metatata based on configuration."; - /// - /// Gets the category. - /// - /// The category. public string Category => "Library"; public string Key => "RefreshLibrary"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs similarity index 100% rename from Emby.Server.Implementations/ScheduledTasks/DailyTrigger.cs rename to Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs diff --git a/Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs similarity index 100% rename from Emby.Server.Implementations/ScheduledTasks/IntervalTrigger.cs rename to Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs diff --git a/Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs similarity index 100% rename from Emby.Server.Implementations/ScheduledTasks/StartupTrigger.cs rename to Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs diff --git a/Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs similarity index 100% rename from Emby.Server.Implementations/ScheduledTasks/WeeklyTrigger.cs rename to Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs index 53ef5d60c..44898d498 100644 --- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs +++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs @@ -3,9 +3,8 @@ using System.IO; using System.Threading.Tasks; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -namespace Emby.Common.Implementations.Serialization +namespace Emby.Server.Implementations.Serialization { /// /// Provides a wrapper around third party json serialization. diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index edea10a07..36975df50 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -18,8 +18,13 @@ namespace Emby.Server.Implementations string appFolderPath, string applicationResourcesPath, string logDirectoryPath = null, - string configurationDirectoryPath = null) - : base(programDataPath, appFolderPath, logDirectoryPath, configurationDirectoryPath) + string configurationDirectoryPath = null, + string cacheDirectoryPath = null) + : base(programDataPath, + appFolderPath, + logDirectoryPath, + configurationDirectoryPath, + cacheDirectoryPath) { ApplicationResourcesPath = applicationResourcesPath; } @@ -136,12 +141,6 @@ namespace Emby.Server.Implementations return path; } - /// - /// Gets the game genre path. - /// - /// The game genre path. - public string GameGenrePath => Path.Combine(InternalMetadataPath, "GameGenre"); - private string _internalMetadataPath; public string InternalMetadataPath { diff --git a/Emby.Server.Implementations/Services/RequestHelper.cs b/Emby.Server.Implementations/Services/RequestHelper.cs index 24e9cbfa4..2563cac99 100644 --- a/Emby.Server.Implementations/Services/RequestHelper.cs +++ b/Emby.Server.Implementations/Services/RequestHelper.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.Services { return contentType == null ? null - : contentType.Split(';')[0].ToLower().Trim(); + : contentType.Split(';')[0].ToLowerInvariant().Trim(); } } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index 16de1a083..dc9975347 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.Services response.ContentType = defaultContentType; } - if (new HashSet { "application/json", }.Contains(response.ContentType)) + if (response.ContentType == "application/json") { response.ContentType += "; charset=utf-8"; } diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 45c918fa1..79f5c59e6 100644 --- a/Emby.Server.Implementations/Services/ServiceExec.cs +++ b/Emby.Server.Implementations/Services/ServiceExec.cs @@ -23,8 +23,6 @@ namespace Emby.Server.Implementations.Services "POLL", "SUBSCRIBE", "UNSUBSCRIBE" }; - public static HashSet AllVerbsSet = new HashSet(AllVerbs); - public static List GetActions(this Type serviceType) { var list = new List(); @@ -98,7 +96,7 @@ namespace Emby.Server.Implementations.Services return Task.FromResult(response); } - var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLower(); + var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLowerInvariant(); throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName())); } diff --git a/Emby.Server.Implementations/Services/ServiceMethod.cs b/Emby.Server.Implementations/Services/ServiceMethod.cs index 7add72815..5018bf4a2 100644 --- a/Emby.Server.Implementations/Services/ServiceMethod.cs +++ b/Emby.Server.Implementations/Services/ServiceMethod.cs @@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.Services public static string Key(Type serviceType, string method, string requestDtoName) { - return serviceType.FullName + " " + method.ToUpper() + " " + requestDtoName; + return serviceType.FullName + " " + method.ToUpperInvariant() + " " + requestDtoName; } } } diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs index 7e1993b71..f575baca3 100644 --- a/Emby.Server.Implementations/Services/ServicePath.cs +++ b/Emby.Server.Implementations/Services/ServicePath.cs @@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Services public static string[] GetPathPartsForMatching(string pathInfo) { - return pathInfo.ToLower().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); + return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries); } public static List GetFirstMatchHashKeys(string[] pathPartsForMatching) @@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.Services this.Description = description; this.restPath = path; - this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpper().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); + this.Verbs = string.IsNullOrWhiteSpace(verbs) ? ServiceExecExtensions.AllVerbs : verbs.ToUpperInvariant().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); var componentsList = new List(); @@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services } else { - this.literalsToMatch[i] = component.ToLower(); + this.literalsToMatch[i] = component.ToLowerInvariant(); if (firstLiteralMatch == null) { @@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Services foreach (var propertyInfo in GetSerializableProperties(RequestType)) { var propertyName = propertyInfo.Name; - propertyNamesMap.Add(propertyName.ToLower(), propertyName); + propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName); } } @@ -483,7 +483,7 @@ namespace Emby.Server.Implementations.Services continue; } - if (!this.propertyNamesMap.TryGetValue(variableName.ToLower(), out var propertyNameOnRequest)) + if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest)) { if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase)) { diff --git a/Emby.Server.Implementations/Services/SwaggerService.cs b/Emby.Server.Implementations/Services/SwaggerService.cs index 9bceeabec..3e6970eef 100644 --- a/Emby.Server.Implementations/Services/SwaggerService.cs +++ b/Emby.Server.Implementations/Services/SwaggerService.cs @@ -216,40 +216,28 @@ namespace Emby.Server.Implementations.Services { var responses = new Dictionary { + { "200", new SwaggerResponse { description = "OK" } } }; - responses["200"] = new SwaggerResponse + var apiKeySecurity = new Dictionary { - description = "OK" + { "api_key", Array.Empty() } }; - var security = new List>(); - - var apiKeySecurity = new Dictionary(); - apiKeySecurity["api_key"] = Array.Empty(); - - security.Add(apiKeySecurity); - - result[verb.ToLower()] = new SwaggerMethod + result[verb.ToLowerInvariant()] = new SwaggerMethod { summary = info.Summary, description = info.Description, - produces = new[] - { - "application/json" - }, - consumes = new[] - { - "application/json" - }, + produces = new[] { "application/json" }, + consumes = new[] { "application/json" }, operationId = info.RequestType.Name, tags = Array.Empty(), - parameters = new SwaggerParam[] { }, + parameters = Array.Empty(), responses = responses, - security = security.ToArray() + security = new [] { apiKeySecurity } }; } diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index b03345e03..fa0ab62d3 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -27,7 +27,6 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -60,7 +59,6 @@ namespace Emby.Server.Implementations.Session private readonly IAuthenticationRepository _authRepo; private readonly IDeviceManager _deviceManager; - private readonly ITimerFactory _timerFactory; /// /// The _active connections @@ -103,8 +101,7 @@ namespace Emby.Server.Implementations.Session IHttpClient httpClient, IAuthenticationRepository authRepo, IDeviceManager deviceManager, - IMediaSourceManager mediaSourceManager, - ITimerFactory timerFactory) + IMediaSourceManager mediaSourceManager) { _userDataManager = userDataManager; _logger = loggerFactory.CreateLogger(nameof(SessionManager)); @@ -119,7 +116,6 @@ namespace Emby.Server.Implementations.Session _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; - _timerFactory = timerFactory; _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; } @@ -503,13 +499,13 @@ namespace Emby.Server.Implementations.Session return users; } - private ITimer _idleTimer; + private Timer _idleTimer; private void StartIdleCheckTimer() { if (_idleTimer == null) { - _idleTimer = _timerFactory.Create(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); + _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } } private void StopIdleCheckTimer() @@ -606,7 +602,7 @@ namespace Emby.Server.Implementations.Session ClearTranscodingInfo(session.DeviceId); } - session.StartAutomaticProgress(_timerFactory, info); + session.StartAutomaticProgress(info); var users = GetUsers(session); @@ -717,7 +713,7 @@ namespace Emby.Server.Implementations.Session if (!isAutomated) { - session.StartAutomaticProgress(_timerFactory, info); + session.StartAutomaticProgress(info); } StartIdleCheckTimer(); diff --git a/Emby.Server.Implementations/Sorting/GameSystemComparer.cs b/Emby.Server.Implementations/Sorting/GameSystemComparer.cs deleted file mode 100644 index 2a04bae3c..000000000 --- a/Emby.Server.Implementations/Sorting/GameSystemComparer.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Implementations.Sorting -{ - public class GameSystemComparer : IBaseItemComparer - { - /// - /// Compares the specified x. - /// - /// The x. - /// The y. - /// System.Int32. - public int Compare(BaseItem x, BaseItem y) - { - return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); - } - - /// - /// Gets the value. - /// - /// The x. - /// System.String. - private static string GetValue(BaseItem x) - { - var game = x as Game; - - if (game != null) - { - return game.GameSystem; - } - - var system = x as GameSystem; - - if (system != null) - { - return system.GameSystemName; - } - - return string.Empty; - } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.GameSystem; - } -} diff --git a/Emby.Server.Implementations/Sorting/PlayersComparer.cs b/Emby.Server.Implementations/Sorting/PlayersComparer.cs deleted file mode 100644 index e3652f36b..000000000 --- a/Emby.Server.Implementations/Sorting/PlayersComparer.cs +++ /dev/null @@ -1,43 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace Emby.Server.Implementations.Sorting -{ - public class PlayersComparer : IBaseItemComparer - { - /// - /// Compares the specified x. - /// - /// The x. - /// The y. - /// System.Int32. - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// - /// Gets the value. - /// - /// The x. - /// System.String. - private static int GetValue(BaseItem x) - { - var game = x as Game; - - if (game != null) - { - return game.PlayersSupported ?? 0; - } - - return 0; - } - - /// - /// Gets the name. - /// - /// The name. - public string Name => ItemSortBy.Players; - } -} diff --git a/Emby.Server.Implementations/StartupOptions.cs b/Emby.Server.Implementations/StartupOptions.cs deleted file mode 100644 index 221263634..000000000 --- a/Emby.Server.Implementations/StartupOptions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Linq; - -namespace Emby.Server.Implementations -{ - public class StartupOptions - { - private readonly string[] _options; - - public StartupOptions(string[] commandLineArgs) - { - _options = commandLineArgs; - } - - public bool ContainsOption(string option) - => _options.Contains(option, StringComparer.OrdinalIgnoreCase); - - public string GetOption(string name) - { - int index = Array.IndexOf(_options, name); - - if (index == -1) - { - return null; - } - - return _options.ElementAtOrDefault(index + 1); - } - } -} diff --git a/Emby.Server.Implementations/Threading/CommonTimer.cs b/Emby.Server.Implementations/Threading/CommonTimer.cs deleted file mode 100644 index 5a05da564..000000000 --- a/Emby.Server.Implementations/Threading/CommonTimer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Threading; -using MediaBrowser.Model.Threading; - -namespace Emby.Server.Implementations.Threading -{ - public class CommonTimer : ITimer - { - private readonly Timer _timer; - - public CommonTimer(Action callback, object state, TimeSpan dueTime, TimeSpan period) - { - _timer = new Timer(new TimerCallback(callback), state, dueTime, period); - } - - public CommonTimer(Action callback, object state, int dueTimeMs, int periodMs) - { - _timer = new Timer(new TimerCallback(callback), state, dueTimeMs, periodMs); - } - - public void Change(TimeSpan dueTime, TimeSpan period) - { - _timer.Change(dueTime, period); - } - - public void Change(int dueTimeMs, int periodMs) - { - _timer.Change(dueTimeMs, periodMs); - } - - public void Dispose() - { - _timer.Dispose(); - } - } -} diff --git a/Emby.Server.Implementations/Threading/TimerFactory.cs b/Emby.Server.Implementations/Threading/TimerFactory.cs deleted file mode 100644 index ca50064c7..000000000 --- a/Emby.Server.Implementations/Threading/TimerFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using MediaBrowser.Model.Threading; - -namespace Emby.Server.Implementations.Threading -{ - public class TimerFactory : ITimerFactory - { - public ITimer Create(Action callback, object state, TimeSpan dueTime, TimeSpan period) - { - return new CommonTimer(callback, state, dueTime, period); - } - - public ITimer Create(Action callback, object state, int dueTimeMs, int periodMs) - { - return new CommonTimer(callback, state, dueTimeMs, periodMs); - } - } -} diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 552155635..dc7f57f27 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -169,10 +169,8 @@ namespace Emby.Server.Implementations.Updates string packageType = null, Version applicationVersion = null) { - // TODO cvium: when plugins get back this would need to be fixed - // var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); - - return new List(); //FilterPackages(packages, packageType, applicationVersion); + var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); + return FilterPackages(packages, packageType, applicationVersion); } /// @@ -184,12 +182,10 @@ namespace Emby.Server.Implementations.Updates { using (var response = await _httpClient.SendAsync(new HttpRequestOptions { - Url = "https://www.mb3admin.local/admin/service/EmbyPackages.json", + Url = "https://repo.jellyfin.org/releases/plugin/manifest.json", CancellationToken = cancellationToken, Progress = new SimpleProgress(), - CacheLength = GetCacheLength(), - CacheMode = CacheMode.Unconditional - + CacheLength = GetCacheLength() }, "GET").ConfigureAwait(false)) { using (var stream = response.Content) diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index 8788cfc26..ce6c2cd87 100644 --- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs @@ -43,10 +43,6 @@ namespace Emby.Server.Implementations.UserViews { includeItemTypes = new string[] { "Book", "AudioBook" }; } - else if (string.Equals(viewType, CollectionType.Games)) - { - includeItemTypes = new string[] { "Game" }; - } else if (string.Equals(viewType, CollectionType.BoxSets)) { includeItemTypes = new string[] { "BoxSet" }; diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs index 937db3f23..4ec68e550 100644 --- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs @@ -12,7 +12,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; namespace Emby.Server.Implementations.UserViews @@ -83,7 +82,8 @@ namespace Emby.Server.Implementations.UserViews return i; - }).DistinctBy(i => i.Id); + }).GroupBy(x => x.Id) + .Select(x => x.First()); if (isUsingCollectionStrip) { diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs index 7629f6039..c810004ab 100644 --- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs +++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs @@ -11,7 +11,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; -namespace Emby.Server.Implementations.Photos +namespace Emby.Server.Implementations.UserViews { public abstract class BaseFolderImageProvider : BaseDynamicImageProvider where T : Folder, new() diff --git a/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/Jellyfin.Drawing.Skia/SkiaEncoder.cs index f1b886ec6..dc714ed18 100644 --- a/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -35,8 +36,8 @@ namespace Jellyfin.Drawing.Skia LogVersion(); } - public string[] SupportedInputFormats => - new[] + public IReadOnlyCollection SupportedInputFormats => + new HashSet(StringComparer.OrdinalIgnoreCase) { "jpeg", "jpg", @@ -62,7 +63,8 @@ namespace Jellyfin.Drawing.Skia "arw" }; - public ImageFormat[] SupportedOutputFormats => new[] { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; + public IReadOnlyCollection SupportedOutputFormats + => new HashSet() { ImageFormat.Webp, ImageFormat.Jpg, ImageFormat.Png }; private void LogVersion() { @@ -253,7 +255,8 @@ namespace Jellyfin.Drawing.Skia } } - private static string[] TransparentImageTypes = new string[] { ".png", ".gif", ".webp" }; + private static readonly HashSet TransparentImageTypes + = new HashSet(StringComparer.OrdinalIgnoreCase) { ".png", ".gif", ".webp" }; internal static SKBitmap Decode(string path, bool forceCleanBitmap, IFileSystem fileSystem, ImageOrientation? orientation, out SKEncodedOrigin origin) { @@ -262,7 +265,7 @@ namespace Jellyfin.Drawing.Skia throw new FileNotFoundException("File not found", path); } - var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path) ?? string.Empty); + var requiresTransparencyHack = TransparentImageTypes.Contains(Path.GetExtension(path)); if (requiresTransparencyHack || forceCleanBitmap) { diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index 92115047c..dfdf39871 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -25,7 +25,7 @@ namespace Jellyfin.Drawing.Skia throw new ArgumentNullException(nameof(outputPath)); } - var ext = Path.GetExtension(outputPath).ToLower(); + var ext = Path.GetExtension(outputPath).ToLowerInvariant(); if (ext == ".jpg" || ext == ".jpeg") return SKEncodedImageFormat.Jpeg; diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index b580f45ca..315e34a04 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; -using Jellyfin.SocketSharp; +using Jellyfin.Server.SocketSharp; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; @@ -16,7 +16,7 @@ namespace Jellyfin.Server { } - public override bool CanSelfRestart => StartupOptions.ContainsOption("-restartpath"); + public override bool CanSelfRestart => StartupOptions.RestartPath != null; protected override void RestartInternal() => Program.Restart(); diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 5a4bf5149..b1515df43 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -32,6 +32,7 @@ + @@ -40,9 +41,8 @@ - - - + + diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 66586d4e4..7826fde35 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -6,8 +6,10 @@ using System.Net; using System.Net.Security; using System.Reflection; using System.Runtime.InteropServices; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; using Emby.Server.Implementations.EnvironmentInfo; @@ -35,19 +37,32 @@ namespace Jellyfin.Server public static async Task Main(string[] args) { - StartupOptions options = new StartupOptions(args); - Version version = Assembly.GetEntryAssembly().GetName().Version; + // For backwards compatibility. + // Modify any input arguments now which start with single-hyphen to POSIX standard + // double-hyphen to allow parsing by CommandLineParser package. + const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx + const string substitution = @"-$1"; // Prepend with additional single-hyphen + var regex = new Regex(pattern); - if (options.ContainsOption("-v") || options.ContainsOption("--version")) + for (var i = 0; i < args.Length; i++) { - Console.WriteLine(version.ToString()); + args[i] = regex.Replace(args[i], substitution); } + // Parse the command line arguments and either start the app or exit indicating error + await Parser.Default.ParseArguments(args) + .MapResult( + options => StartApp(options), + errs => Task.FromResult(0)).ConfigureAwait(false); + } + + private static async Task StartApp(StartupOptions options) + { ServerApplicationPaths appPaths = CreateApplicationPaths(options); // $JELLYFIN_LOG_DIR needs to be set for the logger configuration manager Environment.SetEnvironmentVariable("JELLYFIN_LOG_DIR", appPaths.LogDirectoryPath); - await createLogger(appPaths); + await CreateLogger(appPaths); _logger = _loggerFactory.CreateLogger("Main"); AppDomain.CurrentDomain.UnhandledException += (sender, e) @@ -78,9 +93,9 @@ namespace Jellyfin.Server Shutdown(); }; - _logger.LogInformation("Jellyfin version: {Version}", version); + _logger.LogInformation("Jellyfin version: {Version}", Assembly.GetEntryAssembly().GetName().Version); - EnvironmentInfo environmentInfo = new EnvironmentInfo(getOperatingSystem()); + EnvironmentInfo environmentInfo = new EnvironmentInfo(GetOperatingSystem()); ApplicationHost.LogEnvironmentInfo(_logger, appPaths, environmentInfo); SQLitePCL.Batteries_V2.Init(); @@ -99,12 +114,10 @@ namespace Jellyfin.Server new NullImageEncoder(), new NetworkManager(_loggerFactory, environmentInfo))) { - appHost.Init(); + await appHost.Init(); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); - _logger.LogInformation("Running startup tasks"); - await appHost.RunStartupTasks(); // TODO: read input for a stop command @@ -118,8 +131,6 @@ namespace Jellyfin.Server { // Don't throw on cancellation } - - _logger.LogInformation("Disposing app host"); } if (_restartOnShutdown) @@ -133,9 +144,9 @@ namespace Jellyfin.Server string programDataPath = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); if (string.IsNullOrEmpty(programDataPath)) { - if (options.ContainsOption("-programdata")) + if (options.DataDir != null) { - programDataPath = options.GetOption("-programdata"); + programDataPath = options.DataDir; } else { @@ -171,9 +182,9 @@ namespace Jellyfin.Server string configDir = Environment.GetEnvironmentVariable("JELLYFIN_CONFIG_DIR"); if (string.IsNullOrEmpty(configDir)) { - if (options.ContainsOption("-configdir")) + if (options.ConfigDir != null) { - configDir = options.GetOption("-configdir"); + configDir = options.ConfigDir; } else { @@ -187,12 +198,37 @@ namespace Jellyfin.Server Directory.CreateDirectory(configDir); } + string cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); + if (string.IsNullOrEmpty(cacheDir)) + { + if (options.CacheDir != null) + { + cacheDir = options.CacheDir; + } + else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // $XDG_CACHE_HOME defines the base directory relative to which user specific non-essential data files should be stored. + cacheDir = Environment.GetEnvironmentVariable("XDG_CACHE_HOME"); + // If $XDG_CACHE_HOME is either not set or empty, $HOME/.cache should be used. + if (string.IsNullOrEmpty(cacheDir)) + { + cacheDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache"); + } + cacheDir = Path.Combine(cacheDir, "jellyfin"); + } + } + + if (cacheDir != null) + { + Directory.CreateDirectory(cacheDir); + } + string logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); if (string.IsNullOrEmpty(logDir)) { - if (options.ContainsOption("-logdir")) + if (options.LogDir != null) { - logDir = options.GetOption("-logdir"); + logDir = options.LogDir; } else { @@ -208,10 +244,10 @@ namespace Jellyfin.Server string appPath = AppContext.BaseDirectory; - return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir); + return new ServerApplicationPaths(programDataPath, appPath, appPath, logDir, configDir, cacheDir); } - private static async Task createLogger(IApplicationPaths appPaths) + private static async Task CreateLogger(IApplicationPaths appPaths) { try { @@ -271,7 +307,7 @@ namespace Jellyfin.Server return new NullImageEncoder(); } - private static MediaBrowser.Model.System.OperatingSystem getOperatingSystem() + private static MediaBrowser.Model.System.OperatingSystem GetOperatingSystem() { switch (Environment.OSVersion.Platform) { @@ -315,11 +351,11 @@ namespace Jellyfin.Server Shutdown(); } - private static void StartNewInstance(StartupOptions startupOptions) + private static void StartNewInstance(StartupOptions options) { _logger.LogInformation("Starting new instance"); - string module = startupOptions.GetOption("-restartpath"); + string module = options.RestartPath; if (string.IsNullOrWhiteSpace(module)) { @@ -328,9 +364,9 @@ namespace Jellyfin.Server string commandLineArgsString; - if (startupOptions.ContainsOption("-restartargs")) + if (options.RestartArgs != null) { - commandLineArgsString = startupOptions.GetOption("-restartargs") ?? string.Empty; + commandLineArgsString = options.RestartArgs ?? string.Empty; } else { diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs index 77ce03510..89c75e536 100644 --- a/Jellyfin.Server/SocketSharp/HttpFile.cs +++ b/Jellyfin.Server/SocketSharp/HttpFile.cs @@ -1,7 +1,7 @@ using System.IO; using MediaBrowser.Model.Services; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public class HttpFile : IHttpFile { diff --git a/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs index 017690062..a8ba4cdb5 100644 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ b/Jellyfin.Server/SocketSharp/RequestMono.cs @@ -7,11 +7,11 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public partial class WebSocketSharpRequest : IHttpRequest { - static internal string GetParameter(string header, string attr) + internal static string GetParameter(string header, string attr) { int ap = header.IndexOf(attr); if (ap == -1) @@ -40,7 +40,7 @@ namespace Jellyfin.SocketSharp return header.Substring(ap + 1, end - ap - 1); } - async Task LoadMultiPart(WebROCollection form) + private async Task LoadMultiPart(WebROCollection form) { string boundary = GetParameter(ContentType, "; boundary="); if (boundary == null) @@ -50,8 +50,8 @@ namespace Jellyfin.SocketSharp using (var requestStream = InputStream) { - //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request - //Not ending with \r\n? + // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request + // Not ending with \r\n? var ms = new MemoryStream(32 * 1024); await requestStream.CopyToAsync(ms).ConfigureAwait(false); @@ -62,9 +62,9 @@ namespace Jellyfin.SocketSharp input.Position = 0; // Uncomment to debug - //var content = new StreamReader(ms).ReadToEnd(); - //Console.WriteLine(boundary + "::" + content); - //input.Position = 0; + // var content = new StreamReader(ms).ReadToEnd(); + // Console.WriteLine(boundary + "::" + content); + // input.Position = 0; var multi_part = new HttpMultipart(input, boundary, ContentEncoding); @@ -111,7 +111,7 @@ namespace Jellyfin.SocketSharp // Setting this before calling the validator prevents // possible endless recursion checked_form = true; - ValidateNameValueCollection ("Form", query_string_nvc, RequestValidationSource.Form); + ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); } else #endif if (validate_form && !checked_form) @@ -130,7 +130,7 @@ namespace Jellyfin.SocketSharp protected bool validate_cookies, validate_query_string, validate_form; protected bool checked_cookies, checked_query_string, checked_form; - static void ThrowValidationException(string name, string key, string value) + private static void ThrowValidationException(string name, string key, string value) { string v = "\"" + value + "\""; if (v.Length > 20) @@ -144,7 +144,7 @@ namespace Jellyfin.SocketSharp throw new Exception(msg); } - static void ValidateNameValueCollection(string name, QueryParamCollection coll) + private static void ValidateNameValueCollection(string name, QueryParamCollection coll) { if (coll == null) { @@ -209,7 +209,7 @@ namespace Jellyfin.SocketSharp validate_form = true; } - bool IsContentType(string ct, bool starts_with) + private bool IsContentType(string ct, bool starts_with) { if (ct == null || ContentType == null) { @@ -224,7 +224,7 @@ namespace Jellyfin.SocketSharp return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); } - async Task LoadWwwForm(WebROCollection form) + private async Task LoadWwwForm(WebROCollection form) { using (var input = InputStream) { @@ -280,7 +280,7 @@ namespace Jellyfin.SocketSharp } } - static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) + private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value) { form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString())); @@ -288,9 +288,9 @@ namespace Jellyfin.SocketSharp value.Length = 0; } - Dictionary files; + private Dictionary files; - class WebROCollection : QueryParamCollection + private class WebROCollection : QueryParamCollection { public override string ToString() { @@ -317,16 +317,16 @@ namespace Jellyfin.SocketSharp public sealed class HttpPostedFile { - string name; - string content_type; - Stream stream; + private string name; + private string content_type; + private Stream stream; - class ReadSubStream : Stream + private class ReadSubStream : Stream { - Stream s; - long offset; - long end; - long position; + private Stream s; + private long offset; + private long end; + private long position; public ReadSubStream(Stream s, long offset, long length) { @@ -360,13 +360,13 @@ namespace Jellyfin.SocketSharp int len = buffer.Length; if (dest_offset > len) { - throw new ArgumentException("destination offset is beyond array size"); + throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset)); } // reordered to avoid possible integer overflow if (dest_offset > len - count) { - throw new ArgumentException("Reading would overrun buffer"); + throw new ArgumentException("Reading would overrun buffer", nameof(count)); } if (count > end - position) @@ -429,7 +429,7 @@ namespace Jellyfin.SocketSharp real = position + d; break; default: - throw new ArgumentException(); + throw new ArgumentException(nameof(origin)); } long virt = real - offset; @@ -491,7 +491,7 @@ namespace Jellyfin.SocketSharp public Stream InputStream => stream; } - class Helpers + private class Helpers { public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture; } @@ -528,7 +528,7 @@ namespace Jellyfin.SocketSharp } } - class HttpMultipart + private class HttpMultipart { public class Element @@ -543,19 +543,19 @@ namespace Jellyfin.SocketSharp public override string ToString() { return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " + - Start.ToString() + ", Length " + Length.ToString(); + Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture); } } - Stream data; - string boundary; - byte[] boundary_bytes; - byte[] buffer; - bool at_eof; - Encoding encoding; - StringBuilder sb; + private Stream data; + private string boundary; + private byte[] boundary_bytes; + private byte[] buffer; + private bool at_eof; + private Encoding encoding; + private StringBuilder sb; - const byte LF = (byte)'\n', CR = (byte)'\r'; + private const byte LF = (byte)'\n', CR = (byte)'\r'; // See RFC 2046 // In the case of multipart entities, in which one or more different @@ -610,12 +610,11 @@ namespace Jellyfin.SocketSharp } return sb.ToString(); - } private static string GetContentDispositionAttribute(string l, string name) { - int idx = l.IndexOf(name + "=\""); + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); if (idx < 0) { return null; @@ -638,7 +637,7 @@ namespace Jellyfin.SocketSharp private string GetContentDispositionAttributeWithEncoding(string l, string name) { - int idx = l.IndexOf(name + "=\""); + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); if (idx < 0) { return null; @@ -670,11 +669,12 @@ namespace Jellyfin.SocketSharp { try { - string line = ReadLine(); - while (line == string.Empty) + string line; + do { line = ReadLine(); } + while (line.Length == 0); if (line[0] != '-' || line[1] != '-') { diff --git a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs index d0dcd86eb..f371cb25a 100644 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Microsoft.Extensions.Logging; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public class SharpWebSocket : IWebSocket { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index c7f9f01b5..a44343ab2 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -15,7 +15,7 @@ using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; using SocketHttpListener.Net; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpListener : IHttpListener { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 97550e686..ebeb18ea0 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -11,7 +11,7 @@ using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IResponse = MediaBrowser.Model.Services.IResponse; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public partial class WebSocketSharpRequest : IHttpRequest { @@ -29,12 +29,24 @@ namespace Jellyfin.SocketSharp private static string GetHandlerPathIfAny(string listenerUrl) { - if (listenerUrl == null) return null; + if (listenerUrl == null) + { + return null; + } + var pos = listenerUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase); - if (pos == -1) return null; + if (pos == -1) + { + return null; + } + var startHostUrl = listenerUrl.Substring(pos + "://".Length); var endPos = startHostUrl.IndexOf('/'); - if (endPos == -1) return null; + if (endPos == -1) + { + return null; + } + var endHostUrl = startHostUrl.Substring(endPos + 1); return string.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/'); } @@ -68,15 +80,13 @@ namespace Jellyfin.SocketSharp private string remoteIp; public string RemoteIp => remoteIp ?? - (remoteIp = (CheckBadChars(XForwardedFor)) ?? - (NormalizeIp(CheckBadChars(XRealIp)) ?? + (remoteIp = CheckBadChars(XForwardedFor) ?? + NormalizeIp(CheckBadChars(XRealIp) ?? (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; - // // CheckBadChars - throws on invalid chars to be not found in header name/value - // internal static string CheckBadChars(string name) { if (name == null || name.Length == 0) @@ -85,11 +95,11 @@ namespace Jellyfin.SocketSharp } // VALUE check - //Trim spaces from both ends + // Trim spaces from both ends name = name.Trim(HttpTrimCharacters); - //First, check for correctly formed multi-line value - //Second, check for absenece of CTL characters + // First, check for correctly formed multi-line value + // Second, check for absenece of CTL characters int crlf = 0; for (int i = 0; i < name.Length; ++i) { @@ -186,9 +196,12 @@ namespace Jellyfin.SocketSharp public static string GetResponseContentType(IRequest httpReq) { var specifiedContentType = GetQueryStringContentType(httpReq); - if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; + if (!string.IsNullOrEmpty(specifiedContentType)) + { + return specifiedContentType; + } - var serverDefaultContentType = "application/json"; + const string serverDefaultContentType = "application/json"; var acceptContentTypes = httpReq.AcceptTypes; string defaultContentType = null; @@ -198,7 +211,7 @@ namespace Jellyfin.SocketSharp } var acceptsAnything = false; - var hasDefaultContentType = !string.IsNullOrEmpty(defaultContentType); + var hasDefaultContentType = defaultContentType != null; if (acceptContentTypes != null) { foreach (var acceptsType in acceptContentTypes) @@ -210,9 +223,13 @@ namespace Jellyfin.SocketSharp if (acceptsAnything) { if (hasDefaultContentType) + { return defaultContentType; - if (serverDefaultContentType != null) + } + else if (serverDefaultContentType != null) + { return serverDefaultContentType; + } } } @@ -221,7 +238,7 @@ namespace Jellyfin.SocketSharp return Soap11; } - //We could also send a '406 Not Acceptable', but this is allowed also + // We could also send a '406 Not Acceptable', but this is allowed also return serverDefaultContentType; } @@ -229,11 +246,19 @@ namespace Jellyfin.SocketSharp public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) { - if (contentTypes == null || request.ContentType == null) return false; + if (contentTypes == null || request.ContentType == null) + { + return false; + } + foreach (var contentType in contentTypes) { - if (IsContentType(request, contentType)) return true; + if (IsContentType(request, contentType)) + { + return true; + } } + return false; } @@ -253,10 +278,12 @@ namespace Jellyfin.SocketSharp { return null; } + if (pi[0] == '/') { pi = pi.Substring(1); } + format = LeftPart(pi, '/'); if (format.Length > formatMaxLength) { @@ -264,12 +291,12 @@ namespace Jellyfin.SocketSharp } } - format = LeftPart(format, '.').ToLower(); + format = LeftPart(format, '.'); if (format.Contains("json", StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) + else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } @@ -283,10 +310,9 @@ namespace Jellyfin.SocketSharp { return null; } - var pos = strVal.IndexOf(needle); - return pos == -1 - ? strVal - : strVal.Substring(0, pos); + + var pos = strVal.IndexOf(needle, StringComparison.Ordinal); + return pos == -1 ? strVal : strVal.Substring(0, pos); } public static string HandlerFactoryPath; @@ -329,7 +355,7 @@ namespace Jellyfin.SocketSharp return pathInfo; } - //Wildcard mode relies on this to work out the handlerPath + // Wildcard mode relies on this to work out the handlerPath pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath); if (!string.IsNullOrEmpty(pathInfo)) { @@ -371,6 +397,7 @@ namespace Jellyfin.SocketSharp } } } + if (!pathRootFound) { return null; @@ -433,6 +460,7 @@ namespace Jellyfin.SocketSharp { return null; } + try { return Encoding.GetEncoding(param); @@ -480,10 +508,10 @@ namespace Jellyfin.SocketSharp public static string NormalizePathInfo(string pathInfo, string handlerPath) { - if (handlerPath != null && pathInfo.TrimStart('/').StartsWith( - handlerPath, StringComparison.OrdinalIgnoreCase)) + var trimmed = pathInfo.TrimStart('/'); + if (handlerPath != null && trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) { - return pathInfo.TrimStart('/').Substring(handlerPath.Length); + return trimmed.Substring(handlerPath.Length); } return pathInfo; diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index 21bfac55d..cabc96b23 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -14,7 +14,7 @@ using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; -namespace Jellyfin.SocketSharp +namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpResponse : IHttpResponse { diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs new file mode 100644 index 000000000..5d3f7b171 --- /dev/null +++ b/Jellyfin.Server/StartupOptions.cs @@ -0,0 +1,44 @@ +using CommandLine; +using Emby.Server.Implementations; + +namespace Jellyfin.Server +{ + /// + /// Class used by CommandLine package when parsing the command line arguments. + /// + public class StartupOptions : IStartupOptions + { + [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] + public string DataDir { get; set; } + + [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] + public string CacheDir { get; set; } + + [Option('c', "configdir", Required = false, HelpText = "Path to use for configuration data (user settings and pictures).")] + public string ConfigDir { get; set; } + + [Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")] + public string LogDir { get; set; } + + [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH. Must be specified along with --ffprobe.")] + public string FFmpegPath { get; set; } + + [Option("ffprobe", Required = false, HelpText = "Path to external FFprobe executable to use in place of default found in PATH. Must be specified along with --ffmpeg.")] + public string FFprobePath { get; set; } + + [Option("service", Required = false, HelpText = "Run as headless service.")] + public bool IsService { get; set; } + + [Option("noautorunwebapp", Required = false, HelpText = "Run headless if startup wizard is complete.")] + public bool NoAutoRunWebApp { get; set; } + + [Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")] + public string PackageName { get; set; } + + [Option("restartpath", Required = false, HelpText = "Path to restart script.")] + public string RestartPath { get; set; } + + [Option("restartargs", Required = false, HelpText = "Arguments for restart script.")] + public string RestartArgs { get; set; } + } +} diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 763535129..8dbc26356 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -17,7 +18,6 @@ using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api @@ -47,7 +47,6 @@ namespace MediaBrowser.Api private readonly ISessionManager _sessionManager; private readonly IFileSystem _fileSystem; private readonly IMediaSourceManager _mediaSourceManager; - public readonly ITimerFactory TimerFactory; public readonly IProcessFactory ProcessFactory; /// @@ -58,6 +57,8 @@ namespace MediaBrowser.Api private readonly Dictionary _transcodingLocks = new Dictionary(); + private bool _disposed = false; + /// /// Initializes a new instance of the class. /// @@ -66,20 +67,27 @@ namespace MediaBrowser.Api /// The configuration. /// The file system. /// The media source manager. - public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager, ITimerFactory timerFactory, IProcessFactory processFactory, IHttpResultFactory resultFactory) + public ApiEntryPoint( + ILogger logger, + ISessionManager sessionManager, + IServerConfigurationManager config, + IFileSystem fileSystem, + IMediaSourceManager mediaSourceManager, + IProcessFactory processFactory, + IHttpResultFactory resultFactory) { Logger = logger; _sessionManager = sessionManager; _config = config; _fileSystem = fileSystem; _mediaSourceManager = mediaSourceManager; - TimerFactory = timerFactory; ProcessFactory = processFactory; ResultFactory = resultFactory; - Instance = this; _sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress; _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; + + Instance = this; } public static string[] Split(string value, char separator, bool removeEmpty) @@ -130,7 +138,7 @@ namespace MediaBrowser.Api /// /// Runs this instance. /// - public void Run() + public Task RunAsync() { try { @@ -148,6 +156,8 @@ namespace MediaBrowser.Api { Logger.LogError(ex, "Error deleting encoded media cache"); } + + return Task.CompletedTask; } public EncodingOptions GetEncodingOptions() @@ -162,8 +172,7 @@ namespace MediaBrowser.Api { var path = _config.ApplicationPaths.TranscodingTempPath; - foreach (var file in _fileSystem.GetFilePaths(path, true) - .ToList()) + foreach (var file in _fileSystem.GetFilePaths(path, true)) { _fileSystem.DeleteFile(file); } @@ -184,17 +193,40 @@ namespace MediaBrowser.Api /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { - var list = _activeTranscodingJobs.ToList(); - var jobCount = list.Count; + if (_disposed) + { + return; + } - Parallel.ForEach(list, j => KillTranscodingJob(j, false, path => true)); + if (dispose) + { + // TODO: dispose + } - // Try to allow for some time to kill the ffmpeg processes and delete the partial stream files + var jobs = _activeTranscodingJobs.ToList(); + var jobCount = jobs.Count; + + IEnumerable GetKillJobs() + { + foreach (var job in jobs) + { + yield return KillTranscodingJob(job, false, path => true); + } + } + + // Wait for all processes to be killed if (jobCount > 0) { - var task = Task.Delay(1000); - Task.WaitAll(task); + Task.WaitAll(GetKillJobs().ToArray()); } + + _activeTranscodingJobs.Clear(); + _transcodingLocks.Clear(); + + _sessionManager.PlaybackProgress -= _sessionManager_PlaybackProgress; + _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; + + _disposed = true; } @@ -211,19 +243,20 @@ namespace MediaBrowser.Api /// The state. /// The cancellation token source. /// TranscodingJob. - public TranscodingJob OnTranscodeBeginning(string path, + public TranscodingJob OnTranscodeBeginning( + string path, string playSessionId, string liveStreamId, string transcodingJobId, TranscodingJobType type, - IProcess process, + Process process, string deviceId, StreamState state, CancellationTokenSource cancellationTokenSource) { lock (_activeTranscodingJobs) { - var job = new TranscodingJob(Logger, TimerFactory) + var job = new TranscodingJob(Logger) { Type = type, Path = path, @@ -445,7 +478,7 @@ namespace MediaBrowser.Api /// Called when [transcode kill timer stopped]. /// /// The state. - private void OnTranscodeKillTimerStopped(object state) + private async void OnTranscodeKillTimerStopped(object state) { var job = (TranscodingJob)state; @@ -462,7 +495,7 @@ namespace MediaBrowser.Api Logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - KillTranscodingJob(job, true, path => true); + await KillTranscodingJob(job, true, path => true); } /// @@ -472,9 +505,9 @@ namespace MediaBrowser.Api /// The play session identifier. /// The delete files. /// Task. - internal void KillTranscodingJobs(string deviceId, string playSessionId, Func deleteFiles) + internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func deleteFiles) { - KillTranscodingJobs(j => + return KillTranscodingJobs(j => { if (!string.IsNullOrWhiteSpace(playSessionId)) { @@ -492,7 +525,7 @@ namespace MediaBrowser.Api /// The kill job. /// The delete files. /// Task. - private void KillTranscodingJobs(Func killJob, Func deleteFiles) + private Task KillTranscodingJobs(Func killJob, Func deleteFiles) { var jobs = new List(); @@ -505,13 +538,18 @@ namespace MediaBrowser.Api if (jobs.Count == 0) { - return; + return Task.CompletedTask; } - foreach (var job in jobs) + IEnumerable GetKillJobs() { - KillTranscodingJob(job, false, deleteFiles); + foreach (var job in jobs) + { + yield return KillTranscodingJob(job, false, deleteFiles); + } } + + return Task.WhenAll(GetKillJobs()); } /// @@ -520,7 +558,7 @@ namespace MediaBrowser.Api /// The job. /// if set to true [close live stream]. /// The delete. - private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) + private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) { job.DisposeKillTimer(); @@ -545,7 +583,7 @@ namespace MediaBrowser.Api { if (job.TranscodingThrottler != null) { - job.TranscodingThrottler.Stop(); + job.TranscodingThrottler.Stop().GetAwaiter().GetResult(); } var process = job.Process; @@ -556,7 +594,7 @@ namespace MediaBrowser.Api { try { - Logger.LogInformation("Stopping ffmpeg process with q command for {path}", job.Path); + Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path); //process.Kill(); process.StandardInput.WriteLine("q"); @@ -564,20 +602,20 @@ namespace MediaBrowser.Api // Need to wait because killing is asynchronous if (!process.WaitForExit(5000)) { - Logger.LogInformation("Killing ffmpeg process for {path}", job.Path); + Logger.LogInformation("Killing ffmpeg process for {Path}", job.Path); process.Kill(); } } catch (Exception ex) { - Logger.LogError(ex, "Error killing transcoding job for {path}", job.Path); + Logger.LogError(ex, "Error killing transcoding job for {Path}", job.Path); } } } if (delete(job.Path)) { - DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false); } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) @@ -588,19 +626,19 @@ namespace MediaBrowser.Api } catch (Exception ex) { - Logger.LogError(ex, "Error closing live stream for {path}", job.Path); + Logger.LogError(ex, "Error closing live stream for {Path}", job.Path); } } } - private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) + private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) { if (retryCount >= 10) { return; } - Logger.LogInformation("Deleting partial stream file(s) {0}", path); + Logger.LogInformation("Deleting partial stream file(s) {Path}", path); await Task.Delay(delayMs).ConfigureAwait(false); @@ -621,13 +659,13 @@ namespace MediaBrowser.Api } catch (IOException ex) { - Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path); + Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); - DeletePartialStreamFiles(path, jobType, retryCount + 1, 500); + await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false); } catch (Exception ex) { - Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path); + Logger.LogError(ex, "Error deleting partial stream file(s) {Path}", path); } } @@ -650,8 +688,7 @@ namespace MediaBrowser.Api var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) - .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1) - .ToList(); + .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1); Exception e = null; @@ -669,7 +706,7 @@ namespace MediaBrowser.Api catch (IOException ex) { e = ex; - Logger.LogError(ex, "Error deleting HLS file {path}", file); + Logger.LogError(ex, "Error deleting HLS file {Path}", file); } } @@ -713,7 +750,7 @@ namespace MediaBrowser.Api /// Gets or sets the process. /// /// The process. - public IProcess Process { get; set; } + public Process Process { get; set; } public ILogger Logger { get; private set; } /// /// Gets or sets the active request count. @@ -724,9 +761,7 @@ namespace MediaBrowser.Api /// Gets or sets the kill timer. /// /// The kill timer. - private ITimer KillTimer { get; set; } - - private readonly ITimerFactory _timerFactory; + private Timer KillTimer { get; set; } public string DeviceId { get; set; } @@ -756,10 +791,9 @@ namespace MediaBrowser.Api public DateTime LastPingDate { get; set; } public int PingTimeout { get; set; } - public TranscodingJob(ILogger logger, ITimerFactory timerFactory) + public TranscodingJob(ILogger logger) { Logger = logger; - _timerFactory = timerFactory; } public void StopKillTimer() @@ -802,7 +836,7 @@ namespace MediaBrowser.Api if (KillTimer == null) { //Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId); - KillTimer = _timerFactory.Create(callback, this, intervalMs, Timeout.Infinite); + KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite); } else { diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 8decea5a2..451ee72dd 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Api public static string[] SplitValue(string value, char delim) { - if (string.IsNullOrWhiteSpace(value)) + if (value == null) { return Array.Empty(); } @@ -53,8 +53,14 @@ namespace MediaBrowser.Api public static Guid[] GetGuids(string value) { - // Unfortunately filtermenu.js was using |. This can be deprecated after a while. - return (value ?? string.Empty).Split(new[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries).Select(i => new Guid(i)).ToArray(); + if (value == null) + { + return Array.Empty(); + } + + return value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(i => new Guid(i)) + .ToArray(); } /// @@ -118,7 +124,8 @@ namespace MediaBrowser.Api options.Fields = hasFields.GetItemFields(); } - if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount) || !options.ContainsField(Model.Querying.ItemFields.ChildCount)) + if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount) + || !options.ContainsField(Model.Querying.ItemFields.ChildCount)) { var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty; if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 || @@ -145,8 +152,7 @@ namespace MediaBrowser.Api } } - var hasDtoOptions = request as IHasDtoOptions; - if (hasDtoOptions != null) + if (request is IHasDtoOptions hasDtoOptions) { options.EnableImages = hasDtoOptions.EnableImages ?? true; @@ -228,21 +234,6 @@ namespace MediaBrowser.Api return libraryManager.GetMusicGenre(name); } - protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) - { - if (name.IndexOf(BaseItem.SlugChar) != -1) - { - var result = GetItemFromSlugName(libraryManager, name, dtoOptions); - - if (result != null) - { - return result; - } - } - - return libraryManager.GetGameGenre(name); - } - protected Person GetPerson(string name, ILibraryManager libraryManager, DtoOptions dtoOptions) { if (name.IndexOf(BaseItem.SlugChar) != -1) @@ -309,7 +300,7 @@ namespace MediaBrowser.Api return pathInfo[index]; } - private List Parse(string pathUri) + private string[] Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); @@ -323,7 +314,7 @@ namespace MediaBrowser.Api var args = pathInfo.Split('/'); - return args.Skip(1).ToList(); + return args.Skip(1).ToArray(); } /// @@ -349,10 +340,6 @@ namespace MediaBrowser.Api { item = GetMusicGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("GameGenre", StringComparison.OrdinalIgnoreCase) == 0) - { - item = GetGameGenre(name, libraryManager, dtoOptions); - } else if (type.IndexOf("Studio", StringComparison.OrdinalIgnoreCase) == 0) { item = GetStudio(name, libraryManager, dtoOptions); diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index be80305f9..9caf07cea 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -143,16 +143,6 @@ namespace MediaBrowser.Api }).ToArray(); } - else if (string.Equals(request.IncludeItemTypes, "Game", StringComparison.OrdinalIgnoreCase) || - string.Equals(request.IncludeItemTypes, "GameSystem", StringComparison.OrdinalIgnoreCase)) - { - filters.Genres = _libraryManager.GetGameGenres(genreQuery).Items.Select(i => new NameGuidPair - { - Name = i.Item1.Name, - Id = i.Item1.Id - - }).ToArray(); - } else { filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs deleted file mode 100644 index bb908836c..000000000 --- a/MediaBrowser.Api/GamesService.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api -{ - /// - /// Class GetGameSystemSummaries - /// - [Route("/Games/SystemSummaries", "GET", Summary = "Finds games similar to a given game.")] - public class GetGameSystemSummaries : IReturn - { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - /// - /// Class GamesService - /// - [Authenticated] - public class GamesService : BaseApiService - { - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - /// - /// The _user data repository - /// - private readonly IUserDataManager _userDataRepository; - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - /// - /// The _item repo - /// - private readonly IItemRepository _itemRepo; - /// - /// The _dto service - /// - private readonly IDtoService _dtoService; - - private readonly IAuthorizationContext _authContext; - - /// - /// Initializes a new instance of the class. - /// - /// The user manager. - /// The user data repository. - /// The library manager. - /// The item repo. - /// The dto service. - public GamesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IAuthorizationContext authContext) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - _authContext = authContext; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetGameSystemSummaries request) - { - var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId); - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(GameSystem).Name }, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - }; - - var result = _libraryManager.GetItemList(query) - .Cast() - .Select(i => GetSummary(i, user)) - .ToArray(); - - return ToOptimizedResult(result); - } - - /// - /// Gets the summary. - /// - /// The system. - /// The user. - /// GameSystemSummary. - private GameSystemSummary GetSummary(GameSystem system, User user) - { - var summary = new GameSystemSummary - { - Name = system.GameSystemName, - DisplayName = system.Name - }; - - var items = user == null ? - system.GetRecursiveChildren(i => i is Game) : - system.GetRecursiveChildren(user, new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(Game).Name }, - DtoOptions = new DtoOptions(false) - { - EnableImages = false - } - }); - - var games = items.Cast().ToArray(); - - summary.ClientInstalledGameCount = games.Count(i => i.IsPlaceHolder); - - summary.GameCount = games.Length; - - summary.GameFileExtensions = games.Where(i => !i.IsPlaceHolder).Select(i => Path.GetExtension(i.Path)) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToArray(); - - return summary; - } - } -} diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs index fdf584277..922bd8ed6 100644 --- a/MediaBrowser.Api/Images/ImageByNameService.cs +++ b/MediaBrowser.Api/Images/ImageByNameService.cs @@ -145,7 +145,7 @@ namespace MediaBrowser.Api.Images Theme = supportsThemes ? GetThemeName(i.FullName, path) : null, Context = supportsThemes ? null : GetThemeName(i.FullName, path), - Format = i.Extension.ToLower().TrimStart('.') + Format = i.Extension.ToLowerInvariant().TrimStart('.') }) .OrderBy(i => i.Name) .ToList(); diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 149e54f01..61db7b8d4 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -95,8 +95,6 @@ namespace MediaBrowser.Api.Images [Route("/Artists/{Name}/Images/{Type}/{Index}", "GET")] [Route("/Genres/{Name}/Images/{Type}", "GET")] [Route("/Genres/{Name}/Images/{Type}/{Index}", "GET")] - [Route("/GameGenres/{Name}/Images/{Type}", "GET")] - [Route("/GameGenres/{Name}/Images/{Type}/{Index}", "GET")] [Route("/MusicGenres/{Name}/Images/{Type}", "GET")] [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "GET")] [Route("/Persons/{Name}/Images/{Type}", "GET")] @@ -109,8 +107,6 @@ namespace MediaBrowser.Api.Images [Route("/Artists/{Name}/Images/{Type}/{Index}", "HEAD")] [Route("/Genres/{Name}/Images/{Type}", "HEAD")] [Route("/Genres/{Name}/Images/{Type}/{Index}", "HEAD")] - [Route("/GameGenres/{Name}/Images/{Type}", "HEAD")] - [Route("/GameGenres/{Name}/Images/{Type}/{Index}", "HEAD")] [Route("/MusicGenres/{Name}/Images/{Type}", "HEAD")] [Route("/MusicGenres/{Name}/Images/{Type}/{Index}", "HEAD")] [Route("/Persons/{Name}/Images/{Type}", "HEAD")] @@ -328,7 +324,7 @@ namespace MediaBrowser.Api.Images var fileInfo = _fileSystem.GetFileInfo(info.Path); length = fileInfo.Length; - ImageDimensions size = _imageProcessor.GetImageSize(item, info, true); + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); width = size.Width; height = size.Height; diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 0e7bdc086..f3ea7907f 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -57,12 +57,6 @@ namespace MediaBrowser.Api { } - [Route("/Items/RemoteSearch/Game", "POST")] - [Authenticated] - public class GetGameRemoteSearchResults : RemoteSearchQuery, IReturn> - { - } - [Route("/Items/RemoteSearch/BoxSet", "POST")] [Authenticated] public class GetBoxSetRemoteSearchResults : RemoteSearchQuery, IReturn> @@ -173,13 +167,6 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } - public async Task Post(GetGameRemoteSearchResults request) - { - var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); - - return ToOptimizedResult(result); - } - public async Task Post(GetBoxSetRemoteSearchResults request) { var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index c386b25b8..825732888 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -60,15 +61,15 @@ namespace MediaBrowser.Api _fileSystem = fileSystem; } - public object Get(GetMetadataEditorInfo request) + public async Task Get(GetMetadataEditorInfo request) { var item = _libraryManager.GetItemById(request.ItemId); var info = new MetadataEditorInfo { - ParentalRatingOptions = _localizationManager.GetParentalRatings(), + ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(), ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(), - Countries = _localizationManager.GetCountries(), + Countries = await _localizationManager.GetCountries(), Cultures = _localizationManager.GetCultures() }; @@ -154,11 +155,6 @@ namespace MediaBrowser.Api Name = "Books", Value = "books" }); - list.Add(new NameValuePair - { - Name = "Games", - Value = "games" - }); } list.Add(new NameValuePair diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 12d807a7e..d44b07256 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -265,7 +265,6 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } - [Route("/Games/{Id}/Similar", "GET", Summary = "Finds games similar to a given game.")] [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] [Route("/Albums/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] @@ -369,8 +368,6 @@ namespace MediaBrowser.Api.Library return new string[] { "Series", "Season", "Episode" }; case CollectionType.Books: return new string[] { "Book" }; - case CollectionType.Games: - return new string[] { "Game", "GameSystem" }; case CollectionType.Music: return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" }; case CollectionType.HomeVideos: @@ -554,7 +551,8 @@ namespace MediaBrowser.Api.Library Name = i.Name, DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) }) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToArray(); result.MetadataReaders = plugins @@ -564,7 +562,8 @@ namespace MediaBrowser.Api.Library Name = i.Name, DefaultEnabled = true }) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToArray(); result.SubtitleFetchers = plugins @@ -574,7 +573,8 @@ namespace MediaBrowser.Api.Library Name = i.Name, DefaultEnabled = true }) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToArray(); var typeOptions = new List(); @@ -596,7 +596,8 @@ namespace MediaBrowser.Api.Library Name = i.Name, DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToArray(), ImageFetchers = plugins @@ -607,7 +608,8 @@ namespace MediaBrowser.Api.Library Name = i.Name, DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) - .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()) .ToArray(), SupportedImageTypes = plugins @@ -952,8 +954,6 @@ namespace MediaBrowser.Api.Library { AlbumCount = GetCount(typeof(MusicAlbum), user, request), EpisodeCount = GetCount(typeof(Episode), user, request), - GameCount = GetCount(typeof(Game), user, request), - GameSystemCount = GetCount(typeof(GameSystem), user, request), MovieCount = GetCount(typeof(Movie), user, request), SeriesCount = GetCount(typeof(Series), user, request), SongCount = GetCount(typeof(Audio), user, request), diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 88ed4b525..8fdd726b7 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -875,7 +875,9 @@ namespace MediaBrowser.Api.LiveTv private string GetHashedString(string str) { // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty).ToLower(); + return BitConverter.ToString( + _cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))) + .Replace("-", string.Empty).ToLowerInvariant(); } public void Delete(DeleteListingProvider request) diff --git a/MediaBrowser.Api/LocalizationService.cs b/MediaBrowser.Api/LocalizationService.cs index 3b2e18852..eeff67e13 100644 --- a/MediaBrowser.Api/LocalizationService.cs +++ b/MediaBrowser.Api/LocalizationService.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -81,9 +82,9 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Get(GetCountries request) + public async Task Get(GetCountries request) { - var result = _localization.GetCountries(); + var result = await _localization.GetCountries(); return ToOptimizedResult(result); } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 996087018..91766255f 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -268,7 +268,8 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + .Select(x => x.First()) .Take(itemLimit) .ToList(); @@ -308,7 +309,8 @@ namespace MediaBrowser.Api.Movies EnableGroupByMetadataKey = true, DtoOptions = dtoOptions - }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + }).GroupBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + .Select(x => x.First()) .Take(itemLimit) .ToList(); diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 16819ee37..fbb876dea 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -197,7 +197,7 @@ namespace MediaBrowser.Api throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name)); } - Task.Run(() => _installationManager.InstallPackage(package, true, new SimpleProgress(), CancellationToken.None)); + await _installationManager.InstallPackage(package, true, new SimpleProgress(), CancellationToken.None); } /// diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 4ed83baad..073686298 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -68,10 +69,8 @@ namespace MediaBrowser.Api.Playback protected IDeviceManager DeviceManager { get; private set; } protected ISubtitleEncoder SubtitleEncoder { get; private set; } protected IMediaSourceManager MediaSourceManager { get; private set; } - protected IZipClient ZipClient { get; private set; } protected IJsonSerializer JsonSerializer { get; private set; } - public static IServerApplicationHost AppHost; public static IHttpClient HttpClient; protected IAuthorizationContext AuthorizationContext { get; private set; } @@ -80,21 +79,33 @@ namespace MediaBrowser.Api.Playback /// /// Initializes a new instance of the class. /// - protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) + protected BaseStreamingService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext) { - JsonSerializer = jsonSerializer; - AuthorizationContext = authorizationContext; - ZipClient = zipClient; - MediaSourceManager = mediaSourceManager; - DeviceManager = deviceManager; - SubtitleEncoder = subtitleEncoder; - DlnaManager = dlnaManager; - FileSystem = fileSystem; ServerConfigurationManager = serverConfig; UserManager = userManager; LibraryManager = libraryManager; IsoManager = isoManager; MediaEncoder = mediaEncoder; + FileSystem = fileSystem; + DlnaManager = dlnaManager; + SubtitleEncoder = subtitleEncoder; + DeviceManager = deviceManager; + MediaSourceManager = mediaSourceManager; + JsonSerializer = jsonSerializer; + AuthorizationContext = authorizationContext; + EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); } @@ -135,10 +146,10 @@ namespace MediaBrowser.Api.Playback if (EnableOutputInSubFolder) { - return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLower()); + return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant()); } - return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLower()); + return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant()); } protected virtual bool EnableOutputInSubFolder => false; @@ -187,7 +198,8 @@ namespace MediaBrowser.Api.Playback /// The cancellation token source. /// The working directory. /// Task. - protected async Task StartFfMpeg(StreamState state, + protected async Task StartFfMpeg( + StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource, string workingDirectory = null) @@ -215,24 +227,27 @@ namespace MediaBrowser.Api.Playback var transcodingId = Guid.NewGuid().ToString("N"); var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true); - var process = ApiEntryPoint.Instance.ProcessFactory.Create(new ProcessOptions + var process = new Process() { - CreateNoWindow = true, - UseShellExecute = false, + StartInfo = new ProcessStartInfo() + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = false, - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, + // Must consume both stdout and stderr or deadlocks may occur + //RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, + FileName = MediaEncoder.EncoderPath, + Arguments = commandLineArgs, + WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory, - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true, - WorkingDirectory = !string.IsNullOrWhiteSpace(workingDirectory) ? workingDirectory : null - }); + ErrorDialog = false + }, + EnableRaisingEvents = true + }; var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, state.Request.PlaySessionId, @@ -248,13 +263,17 @@ namespace MediaBrowser.Api.Playback Logger.LogInformation(commandLineLogMessage); var logFilePrefix = "ffmpeg-transcode"; - if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + if (state.VideoRequest != null) { - logFilePrefix = "ffmpeg-directstream"; - } - else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - logFilePrefix = "ffmpeg-remux"; + if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) + && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + logFilePrefix = "ffmpeg-directstream"; + } + else if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) + { + logFilePrefix = "ffmpeg-remux"; + } } var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt"); @@ -317,7 +336,7 @@ namespace MediaBrowser.Api.Playback { if (EnableThrottling(state)) { - transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, ApiEntryPoint.Instance.TimerFactory, FileSystem); + transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager, FileSystem); state.TranscodingThrottler.Start(); } } @@ -341,7 +360,7 @@ namespace MediaBrowser.Api.Playback /// The process. /// The job. /// The state. - private void OnFfMpegProcessExited(IProcess process, TranscodingJob job, StreamState state) + private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) { if (job != null) { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 08a2183f8..1acc42ea5 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -54,17 +54,6 @@ namespace MediaBrowser.Api.Playback.Hls /// The type of the transcoding job. protected override TranscodingJobType TranscodingJobType => TranscodingJobType.Hls; - /// - /// Processes the request. - /// - /// The request. - /// if set to true [is live]. - /// System.Object. - protected async Task ProcessRequest(StreamRequest request, bool isLive) - { - return await ProcessRequestAsync(request, isLive).ConfigureAwait(false); - } - /// /// Processes the request async. /// @@ -74,7 +63,7 @@ namespace MediaBrowser.Api.Playback.Hls /// A video bitrate is required /// or /// An audio bitrate is required - private async Task ProcessRequestAsync(StreamRequest request, bool isLive) + protected async Task ProcessRequestAsync(StreamRequest request, bool isLive) { var cancellationTokenSource = new CancellationTokenSource(); @@ -324,7 +313,31 @@ namespace MediaBrowser.Api.Playback.Hls return 0; } - public BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) + public BaseHlsService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext) { } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 30696ec97..45f003cae 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -95,7 +95,32 @@ namespace MediaBrowser.Api.Playback.Hls public class DynamicHlsService : BaseHlsService { - public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) + public DynamicHlsService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + INetworkManager networkManager) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext) { NetworkManager = networkManager; } @@ -209,7 +234,7 @@ namespace MediaBrowser.Api.Playback.Hls // If the playlist doesn't already exist, startup ffmpeg try { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); + await ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { @@ -233,7 +258,7 @@ namespace MediaBrowser.Api.Playback.Hls job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); if (job.TranscodingThrottler != null) { - job.TranscodingThrottler.UnpauseTranscoding(); + await job.TranscodingThrottler.UnpauseTranscoding(); } } } diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs index 02b837912..6a2c7ae03 100644 --- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs +++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs @@ -102,9 +102,9 @@ namespace MediaBrowser.Api.Playback.Hls return GetFileResult(file, file); } - public void Delete(StopEncodingProcess request) + public Task Delete(StopEncodingProcess request) { - ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true); + return ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, path => true); } /// diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 142dc2dfd..eb1bbfb74 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; @@ -24,9 +25,9 @@ namespace MediaBrowser.Api.Playback.Hls [Authenticated] public class VideoHlsService : BaseHlsService { - public object Get(GetLiveHlsStream request) + public Task Get(GetLiveHlsStream request) { - return ProcessRequest(request, true); + return ProcessRequestAsync(request, true); } /// @@ -130,7 +131,31 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) + public VideoHlsService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext) { } } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 150571ce9..208a5560d 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -32,7 +32,33 @@ namespace MediaBrowser.Api.Playback.Progressive //[Authenticated] public class AudioService : BaseProgressiveStreamingService { - public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo) + public AudioService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + IEnvironmentInfo environmentInfo) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + environmentInfo) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 621d1ff88..c197de173 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -25,12 +25,35 @@ namespace MediaBrowser.Api.Playback.Progressive /// public abstract class BaseProgressiveStreamingService : BaseStreamingService { - protected readonly IImageProcessor ImageProcessor; protected readonly IEnvironmentInfo EnvironmentInfo; - public BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext) + public BaseProgressiveStreamingService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + IEnvironmentInfo environmentInfo) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext) { - ImageProcessor = imageProcessor; EnvironmentInfo = environmentInfo; } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 00b79ccdd..a0ea5c62d 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -68,7 +68,33 @@ namespace MediaBrowser.Api.Playback.Progressive //[Authenticated] public class VideoService : BaseProgressiveStreamingService { - public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, IEnvironmentInfo environmentInfo) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer, authorizationContext, imageProcessor, environmentInfo) + public VideoService( + IServerConfigurationManager serverConfig, + IUserManager userManager, + ILibraryManager libraryManager, + IIsoManager isoManager, + IMediaEncoder mediaEncoder, + IFileSystem fileSystem, + IDlnaManager dlnaManager, + ISubtitleEncoder subtitleEncoder, + IDeviceManager deviceManager, + IMediaSourceManager mediaSourceManager, + IJsonSerializer jsonSerializer, + IAuthorizationContext authorizationContext, + IEnvironmentInfo environmentInfo) + : base(serverConfig, + userManager, + libraryManager, + isoManager, + mediaEncoder, + fileSystem, + dlnaManager, + subtitleEncoder, + deviceManager, + mediaSourceManager, + jsonSerializer, + authorizationContext, + environmentInfo) { } diff --git a/MediaBrowser.Api/Playback/TranscodingThrottler.cs b/MediaBrowser.Api/Playback/TranscodingThrottler.cs index 97f21c8f3..0e73d77ef 100644 --- a/MediaBrowser.Api/Playback/TranscodingThrottler.cs +++ b/MediaBrowser.Api/Playback/TranscodingThrottler.cs @@ -1,8 +1,9 @@ using System; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback @@ -11,18 +12,16 @@ namespace MediaBrowser.Api.Playback { private readonly TranscodingJob _job; private readonly ILogger _logger; - private ITimer _timer; + private Timer _timer; private bool _isPaused; private readonly IConfigurationManager _config; - private readonly ITimerFactory _timerFactory; private readonly IFileSystem _fileSystem; - public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, ITimerFactory timerFactory, IFileSystem fileSystem) + public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config, IFileSystem fileSystem) { _job = job; _logger = logger; _config = config; - _timerFactory = timerFactory; _fileSystem = fileSystem; } @@ -33,10 +32,10 @@ namespace MediaBrowser.Api.Playback public void Start() { - _timer = _timerFactory.Create(TimerCallback, null, 5000, 5000); + _timer = new Timer(TimerCallback, null, 5000, 5000); } - private void TimerCallback(object state) + private async void TimerCallback(object state) { if (_job.HasExited) { @@ -48,15 +47,15 @@ namespace MediaBrowser.Api.Playback if (options.EnableThrottling && IsThrottleAllowed(_job, options.ThrottleDelaySeconds)) { - PauseTranscoding(); + await PauseTranscoding(); } else { - UnpauseTranscoding(); + await UnpauseTranscoding(); } } - private void PauseTranscoding() + private async Task PauseTranscoding() { if (!_isPaused) { @@ -64,7 +63,7 @@ namespace MediaBrowser.Api.Playback try { - _job.Process.StandardInput.Write("c"); + await _job.Process.StandardInput.WriteAsync("c"); _isPaused = true; } catch (Exception ex) @@ -74,7 +73,7 @@ namespace MediaBrowser.Api.Playback } } - public void UnpauseTranscoding() + public async Task UnpauseTranscoding() { if (_isPaused) { @@ -82,7 +81,7 @@ namespace MediaBrowser.Api.Playback try { - _job.Process.StandardInput.WriteLine(); + await _job.Process.StandardInput.WriteLineAsync(); _isPaused = false; } catch (Exception ex) @@ -153,10 +152,10 @@ namespace MediaBrowser.Api.Playback return false; } - public void Stop() + public async Task Stop() { DisposeTimer(); - UnpauseTranscoding(); + await UnpauseTranscoding(); } public void Dispose() diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index 1aa77792c..e07770a4c 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -287,7 +287,6 @@ namespace MediaBrowser.Api.Playback SubtitleEncoder, DeviceManager, MediaSourceManager, - ZipClient, JsonSerializer, AuthorizationContext, NetworkManager) @@ -334,10 +333,8 @@ namespace MediaBrowser.Api.Playback SubtitleEncoder, DeviceManager, MediaSourceManager, - ZipClient, JsonSerializer, AuthorizationContext, - ImageProcessor, EnvironmentInfo) { Request = Request diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index c9a11c117..f011e6e41 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -194,7 +194,7 @@ namespace MediaBrowser.Api.Session [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] public string Id { get; set; } - [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string PlayableMediaTypes { get; set; } [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 3d59b4c9a..53ba7eefd 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -39,7 +38,7 @@ namespace MediaBrowser.Api } [Route("/Startup/User", "POST", Summary = "Updates initial user info", IsHidden = true)] - public class UpdateStartupUser : StartupUser, IReturn + public class UpdateStartupUser : StartupUser { } @@ -102,12 +101,11 @@ namespace MediaBrowser.Api return new StartupUser { Name = user.Name, - ConnectUserName = user.ConnectUserName, Password = user.Password }; } - public async Task Post(UpdateStartupUser request) + public async Task Post(UpdateStartupUser request) { var user = _userManager.Users.First(); @@ -118,10 +116,6 @@ namespace MediaBrowser.Api if (!string.IsNullOrEmpty(request.Password)) { await _userManager.ChangePassword(user, request.Password).ConfigureAwait(false); } - - var result = new UpdateStartupUserResult(); - - return result; } } @@ -135,12 +129,6 @@ namespace MediaBrowser.Api public class StartupUser { public string Name { get; set; } - public string ConnectUserName { get; set; } public string Password { get; set; } } - - public class UpdateStartupUserResult - { - public UserLinkResult UserLinkResult { get; set; } - } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 6e577c53e..471b41127 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -224,7 +224,6 @@ namespace MediaBrowser.Api.UserLibrary dto.TrailerCount = counts.TrailerCount; dto.AlbumCount = counts.AlbumCount; dto.SongCount = counts.SongCount; - dto.GameCount = counts.GameCount; dto.ArtistCount = counts.ArtistCount; } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index 4cccc0cb5..7af50c329 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -42,12 +42,6 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? MinIndexNumber { get; set; } - [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinPlayers { get; set; } - - [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MaxPlayers { get; set; } - [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] public int? ParentIndexNumber { get; set; } diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs deleted file mode 100644 index 3e0d4aca4..000000000 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Services; - -namespace MediaBrowser.Api.UserLibrary -{ - [Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")] - public class GetGameGenres : GetItemsByName - { - } - - [Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")] - public class GetGameGenre : IReturn - { - /// - /// Gets or sets the name. - /// - /// The name. - [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Name { get; set; } - - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } - } - - [Authenticated] - public class GameGenresService : BaseItemsByNameService - { - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetGameGenre request) - { - var result = GetItem(request); - - return ToOptimizedResult(result); - } - - /// - /// Gets the item. - /// - /// The request. - /// Task{BaseItemDto}. - private BaseItemDto GetItem(GetGameGenre request) - { - var dtoOptions = GetDtoOptions(AuthorizationContext, request); - - var item = GetGameGenre(request.Name, LibraryManager, dtoOptions); - - if (!request.UserId.Equals(Guid.Empty)) - { - var user = UserManager.GetUserById(request.UserId); - - return DtoService.GetBaseItemDto(item, dtoOptions, user); - } - - return DtoService.GetBaseItemDto(item, dtoOptions); - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetGameGenres request) - { - var result = GetResultSlim(request); - - return ToOptimizedResult(result); - } - - protected override QueryResult> GetItems(GetItemsByName request, InternalItemsQuery query) - { - return LibraryManager.GetGameGenres(query); - } - - /// - /// Gets all items. - /// - /// The request. - /// The items. - /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IList items) - { - throw new NotImplementedException(); - } - - public GameGenresService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, IItemRepository itemRepository, IDtoService dtoService, IAuthorizationContext authorizationContext) : base(userManager, libraryManager, userDataRepository, itemRepository, dtoService, authorizationContext) - { - } - } -} diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index e20428ead..baf570d50 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -101,11 +101,6 @@ namespace MediaBrowser.Api.UserLibrary return LibraryManager.GetMusicGenres(query); } - if (string.Equals(viewType, CollectionType.Games)) - { - return LibraryManager.GetGameGenres(query); - } - return LibraryManager.GetGenres(query); } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index bf453576c..3ae7da007 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -304,8 +304,6 @@ namespace MediaBrowser.Api.UserLibrary VideoTypes = request.GetVideoTypes(), AdjacentTo = request.AdjacentTo, ItemIds = GetGuids(request.Ids), - MinPlayers = request.MinPlayers, - MaxPlayers = request.MaxPlayers, MinCommunityRating = request.MinCommunityRating, MinCriticRating = request.MinCriticRating, ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? Guid.Empty : new Guid(request.ParentId), diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 72a943092..b40a92a7c 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -247,14 +247,14 @@ namespace MediaBrowser.Api.UserLibrary /// Posts the specified request. /// /// The request. - public async Task Post(MarkPlayedItem request) + public object Post(MarkPlayedItem request) { - var result = await MarkPlayed(request).ConfigureAwait(false); + var result = MarkPlayed(request); return ToOptimizedResult(result); } - private async Task MarkPlayed(MarkPlayedItem request) + private UserItemDataDto MarkPlayed(MarkPlayedItem request) { var user = _userManager.GetUserById(request.UserId); @@ -366,9 +366,9 @@ namespace MediaBrowser.Api.UserLibrary /// Posts the specified request. /// /// The request. - public void Delete(OnPlaybackStopped request) + public Task Delete(OnPlaybackStopped request) { - Post(new ReportPlaybackStopped + return Post(new ReportPlaybackStopped { ItemId = new Guid(request.Id), PositionTicks = request.PositionTicks, @@ -379,20 +379,18 @@ namespace MediaBrowser.Api.UserLibrary }); } - public void Post(ReportPlaybackStopped request) + public async Task Post(ReportPlaybackStopped request) { Logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) { - ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); + await ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); } request.SessionId = GetSession(_sessionContext).Id; - var task = _sessionManager.OnPlaybackStopped(request); - - Task.WaitAll(task); + await _sessionManager.OnPlaybackStopped(request); } /// @@ -403,10 +401,10 @@ namespace MediaBrowser.Api.UserLibrary { var task = MarkUnplayed(request); - return ToOptimizedResult(task.Result); + return ToOptimizedResult(task); } - private async Task MarkUnplayed(MarkUnplayedItem request) + private UserItemDataDto MarkUnplayed(MarkUnplayedItem request) { var user = _userManager.GetUserById(request.UserId); diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 2ef18d7cf..a6849f75f 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Connect; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Services; using MediaBrowser.Model.Users; @@ -299,11 +298,6 @@ namespace MediaBrowser.Api users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value); } - if (request.IsGuest.HasValue) - { - users = users.Where(i => (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest) == request.IsGuest.Value); - } - if (filterByDevice) { var deviceId = _authContext.GetAuthorizationInfo(Request).DeviceId; diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 385127c54..59e3c1767 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Common /// /// if set to true [manage liftime]. /// IEnumerable{``0}. - IEnumerable GetExports(bool manageLiftime = true); + IEnumerable GetExports(bool manageLifetime = true); /// /// Updates the application. @@ -131,7 +131,7 @@ namespace MediaBrowser.Common /// /// Inits this instance. /// - void Init(); + Task Init(); /// /// Creates the instance. diff --git a/MediaBrowser.Controller/Connect/UserLinkResult.cs b/MediaBrowser.Controller/Connect/UserLinkResult.cs deleted file mode 100644 index 327ceb952..000000000 --- a/MediaBrowser.Controller/Connect/UserLinkResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MediaBrowser.Controller.Connect -{ - public class UserLinkResult - { - public bool IsPending { get; set; } - public bool IsNewUserInvitation { get; set; } - public string GuestDisplayName { get; set; } - } -} diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 5b8c9da6f..4eaecd0a0 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using MediaBrowser.Model.Drawing; namespace MediaBrowser.Controller.Drawing @@ -9,12 +10,12 @@ namespace MediaBrowser.Controller.Drawing /// Gets the supported input formats. /// /// The supported input formats. - string[] SupportedInputFormats { get; } + IReadOnlyCollection SupportedInputFormats { get; } /// /// Gets the supported output formats. /// /// The supported output formats. - ImageFormat[] SupportedOutputFormats { get; } + IReadOnlyCollection SupportedOutputFormats { get; } /// /// Encodes the image. diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index 783182730..a11e2186f 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Drawing /// Gets the supported input formats. /// /// The supported input formats. - string[] SupportedInputFormats { get; } + IReadOnlyCollection SupportedInputFormats { get; } /// /// Gets the image enhancers. @@ -26,16 +26,29 @@ namespace MediaBrowser.Controller.Drawing /// The image enhancers. IImageEnhancer[] ImageEnhancers { get; } - ImageDimensions GetImageSize(string path); + /// + /// Gets the dimensions of the image. + /// + /// Path to the image file. + /// ImageDimensions + ImageDimensions GetImageDimensions(string path); /// - /// Gets the size of the image. + /// Gets the dimensions of the image. /// + /// The base item. /// The information. - /// ImageSize. - ImageDimensions GetImageSize(BaseItem item, ItemImageInfo info); + /// ImageDimensions + ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info); - ImageDimensions GetImageSize(BaseItem item, ItemImageInfo info, bool updateItem); + /// + /// Gets the dimensions of the image. + /// + /// The base item. + /// The information. + /// Whether or not the item info should be updated. + /// ImageDimensions + ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem); /// /// Adds the parts. @@ -96,8 +109,8 @@ namespace MediaBrowser.Controller.Drawing /// /// Gets the supported image output formats. /// - /// ImageOutputFormat[]. - ImageFormat[] GetSupportedImageOutputFormats(); + /// IReadOnlyCollection{ImageOutput}. + IReadOnlyCollection GetSupportedImageOutputFormats(); /// /// Creates the image collage. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 482d14e11..72c4e3573 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -615,7 +615,7 @@ namespace MediaBrowser.Controller.Entities if (!string.IsNullOrEmpty(ForcedSortName)) { // Need the ToLower because that's what CreateSortName does - _sortName = ModifySortChunks(ForcedSortName).ToLower(); + _sortName = ModifySortChunks(ForcedSortName).ToLowerInvariant(); } else { @@ -661,7 +661,7 @@ namespace MediaBrowser.Controller.Entities return Name.TrimStart(); } - var sortable = Name.Trim().ToLower(); + var sortable = Name.Trim().ToLowerInvariant(); foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters) { diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 91cfcd0ce..275052d48 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -335,7 +335,11 @@ namespace MediaBrowser.Controller.Entities .OfType() .ToList(); - return PhysicalLocations.Where(i => !FileSystem.AreEqual(i, Path)).SelectMany(i => GetPhysicalParents(i, rootChildren)).DistinctBy(i => i.Id); + return PhysicalLocations + .Where(i => !FileSystem.AreEqual(i, Path)) + .SelectMany(i => GetPhysicalParents(i, rootChildren)) + .GroupBy(x => x.Id) + .Select(x => x.First()); } private IEnumerable GetPhysicalParents(string path, List rootChildren) diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index dab96509c..8bfadbee6 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1149,16 +1149,6 @@ namespace MediaBrowser.Controller.Entities return false; } - if (request.MinPlayers.HasValue) - { - return false; - } - - if (request.MaxPlayers.HasValue) - { - return false; - } - if (request.MinCommunityRating.HasValue) { return false; diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs deleted file mode 100644 index eea1bf43d..000000000 --- a/MediaBrowser.Controller/Entities/Game.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; - -namespace MediaBrowser.Controller.Entities -{ - public class Game : BaseItem, IHasTrailers, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo - { - public Game() - { - MultiPartGameFiles = Array.Empty(); - RemoteTrailers = EmptyMediaUrlArray; - LocalTrailerIds = Array.Empty(); - RemoteTrailerIds = Array.Empty(); - } - - public Guid[] LocalTrailerIds { get; set; } - public Guid[] RemoteTrailerIds { get; set; } - - public override bool CanDownload() - { - return IsFileProtocol; - } - - [IgnoreDataMember] - public override bool SupportsThemeMedia => true; - - [IgnoreDataMember] - public override bool SupportsPeople => false; - - /// - /// Gets the type of the media. - /// - /// The type of the media. - [IgnoreDataMember] - public override string MediaType => Model.Entities.MediaType.Game; - - /// - /// Gets or sets the players supported. - /// - /// The players supported. - public int? PlayersSupported { get; set; } - - /// - /// Gets a value indicating whether this instance is place holder. - /// - /// true if this instance is place holder; otherwise, false. - public bool IsPlaceHolder { get; set; } - - /// - /// Gets or sets the game system. - /// - /// The game system. - public string GameSystem { get; set; } - - /// - /// Gets or sets a value indicating whether this instance is multi part. - /// - /// true if this instance is multi part; otherwise, false. - public bool IsMultiPart { get; set; } - - /// - /// Holds the paths to the game files in the event this is a multipart game - /// - public string[] MultiPartGameFiles { get; set; } - - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - var id = this.GetProviderId(MetadataProviders.Gamesdb); - - if (!string.IsNullOrEmpty(id)) - { - list.Insert(0, "Game-Gamesdb-" + id); - } - return list; - } - - public override IEnumerable GetDeletePaths() - { - if (!IsInMixedFolder) - { - return new[] { - new FileSystemMetadata - { - FullName = System.IO.Path.GetDirectoryName(Path), - IsDirectory = true - } - }; - } - - return base.GetDeletePaths(); - } - - public override UnratedItem GetBlockUnratedType() - { - return UnratedItem.Game; - } - - public GameInfo GetLookupInfo() - { - var id = GetItemLookupInfo(); - - id.GameSystem = GameSystem; - - return id; - } - } -} diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs deleted file mode 100644 index c0fd4ae89..000000000 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Controller.Entities -{ - public class GameGenre : BaseItem, IItemByName - { - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics()); - return list; - } - - public override string CreatePresentationUniqueKey() - { - return GetUserDataKeys()[0]; - } - - public override double GetDefaultPrimaryImageAspectRatio() - { - return 1; - } - - /// - /// Returns the folder containing the item. - /// If the item is a folder, it returns the folder itself - /// - /// The containing folder path. - [IgnoreDataMember] - public override string ContainingFolderPath => Path; - - [IgnoreDataMember] - public override bool SupportsAncestors => false; - - public override bool IsSaveLocalMetadataEnabled() - { - return true; - } - - public override bool CanDelete() - { - return false; - } - - public IList GetTaggedItems(InternalItemsQuery query) - { - query.GenreIds = new[] { Id }; - query.IncludeItemTypes = new[] { typeof(Game).Name }; - - return LibraryManager.GetItemList(query); - } - - [IgnoreDataMember] - public override bool SupportsPeople => false; - - public static string GetPath(string name) - { - return GetPath(name, true); - } - - public static string GetPath(string name, bool normalizeName) - { - // Trim the period at the end because windows will have a hard time with that - var validName = normalizeName ? - FileSystem.GetValidFilename(name).Trim().TrimEnd('.') : - name; - - return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.GameGenrePath, validName); - } - - private string GetRebasedPath() - { - return GetPath(System.IO.Path.GetFileName(Path), false); - } - - public override bool RequiresRefresh() - { - var newPath = GetRebasedPath(); - if (!string.Equals(Path, newPath, StringComparison.Ordinal)) - { - Logger.LogDebug("{0} path has changed from {1} to {2}", GetType().Name, Path, newPath); - return true; - } - return base.RequiresRefresh(); - } - - /// - /// This is called before any metadata refresh and returns true or false indicating if changes were made - /// - public override bool BeforeMetadataRefresh(bool replaceAllMetdata) - { - var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); - - var newPath = GetRebasedPath(); - if (!string.Equals(Path, newPath, StringComparison.Ordinal)) - { - Path = newPath; - hasChanges = true; - } - - return hasChanges; - } - } -} diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs deleted file mode 100644 index 63f830d25..000000000 --- a/MediaBrowser.Controller/Entities/GameSystem.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections.Generic; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; - -namespace MediaBrowser.Controller.Entities -{ - /// - /// Class GameSystem - /// - public class GameSystem : Folder, IHasLookupInfo - { - /// - /// Return the id that should be used to key display prefs for this item. - /// Default is based on the type for everything except actual generic folders. - /// - /// The display prefs id. - [IgnoreDataMember] - public override Guid DisplayPreferencesId => Id; - - [IgnoreDataMember] - public override bool SupportsPlayedStatus => false; - - [IgnoreDataMember] - public override bool SupportsInheritedParentImages => false; - - public override double GetDefaultPrimaryImageAspectRatio() - { - double value = 16; - value /= 9; - - return value; - } - - /// - /// Gets or sets the game system. - /// - /// The game system. - public string GameSystemName { get; set; } - - public override List GetUserDataKeys() - { - var list = base.GetUserDataKeys(); - - if (!string.IsNullOrEmpty(GameSystemName)) - { - list.Insert(0, "GameSystem-" + GameSystemName); - } - return list; - } - - protected override bool GetBlockUnratedValue(UserPolicy config) - { - // Don't block. Determine by game - return false; - } - - public override UnratedItem GetBlockUnratedType() - { - return UnratedItem.Game; - } - - public GameSystemInfo GetLookupInfo() - { - var id = GetItemLookupInfo(); - - id.Path = Path; - - return id; - } - - [IgnoreDataMember] - public override bool SupportsPeople => false; - } -} diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 3f3ab3551..44cb62d22 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Entities public IList GetTaggedItems(InternalItemsQuery query) { query.GenreIds = new[] { Id }; - query.ExcludeItemTypes = new[] { typeof(Game).Name, typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name }; + query.ExcludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name }; return LibraryManager.GetItemList(query); } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 4b7af5391..78f859069 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -95,9 +95,6 @@ namespace MediaBrowser.Controller.Entities public bool? IsKids { get; set; } public bool? IsNews { get; set; } public bool? IsSeries { get; set; } - - public int? MinPlayers { get; set; } - public int? MaxPlayers { get; set; } public int? MinIndexNumber { get; set; } public int? AiredDuringSeason { get; set; } public double? MinCriticRating { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 4539ab0f2..570e9389e 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -270,7 +270,7 @@ namespace MediaBrowser.Controller.Entities.TV // This depends on settings for that series // When this happens, remove the duplicate from season 0 - return allEpisodes.DistinctBy(i => i.Id).Reverse(); + return allEpisodes.GroupBy(i => i.Id).Select(x => x.First()).Reverse(); } public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress progress, CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 06bae9211..0d5f508dd 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Connect; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; @@ -33,11 +32,6 @@ namespace MediaBrowser.Controller.Entities public string EasyPassword { get; set; } public string Salt { get; set; } - public string ConnectUserName { get; set; } - public string ConnectUserId { get; set; } - public UserLinkType? ConnectLinkType { get; set; } - public string ConnectAccessKey { get; set; } - // Strictly to remove IgnoreDataMember public override ItemImageInfo[] ImageInfos { diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index de4105df9..3e2191376 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -150,7 +150,6 @@ namespace MediaBrowser.Controller.Entities private static string[] OriginalFolderViewTypes = new string[] { - MediaBrowser.Model.Entities.CollectionType.Games, MediaBrowser.Model.Entities.CollectionType.Books, MediaBrowser.Model.Entities.CollectionType.MusicVideos, MediaBrowser.Model.Entities.CollectionType.HomeVideos, diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 0b0134669..683218a9e 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -848,52 +848,6 @@ namespace MediaBrowser.Controller.Entities } } - if (query.MinPlayers.HasValue) - { - var filterValue = query.MinPlayers.Value; - - var game = item as Game; - - if (game != null) - { - var players = game.PlayersSupported ?? 1; - - var ok = players >= filterValue; - - if (!ok) - { - return false; - } - } - else - { - return false; - } - } - - if (query.MaxPlayers.HasValue) - { - var filterValue = query.MaxPlayers.Value; - - var game = item as Game; - - if (game != null) - { - var players = game.PlayersSupported ?? 1; - - var ok = players <= filterValue; - - if (!ok) - { - return false; - } - } - else - { - return false; - } - } - if (query.MinCommunityRating.HasValue) { var val = query.MinCommunityRating.Value; diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 33a967758..31cd42975 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -305,7 +305,7 @@ namespace MediaBrowser.Controller.Entities private string GetUserDataKey(string providerId) { - var key = providerId + "-" + ExtraType.ToString().ToLower(); + var key = providerId + "-" + ExtraType.ToString().ToLowerInvariant(); // Make sure different trailers have their own data. if (RunTimeTicks.HasValue) diff --git a/MediaBrowser.Controller/Extensions/StringExtensions.cs b/MediaBrowser.Controller/Extensions/StringExtensions.cs index 73f529fc0..b1aaf6534 100644 --- a/MediaBrowser.Controller/Extensions/StringExtensions.cs +++ b/MediaBrowser.Controller/Extensions/StringExtensions.cs @@ -1,4 +1,8 @@ -using MediaBrowser.Model.Globalization; +using System; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; namespace MediaBrowser.Controller.Extensions { @@ -7,11 +11,45 @@ namespace MediaBrowser.Controller.Extensions /// public static class StringExtensions { - public static ILocalizationManager LocalizationManager { get; set; } - public static string RemoveDiacritics(this string text) { - return LocalizationManager.RemoveDiacritics(text); + if (text == null) + { + throw new ArgumentNullException(nameof(text)); + } + + var chars = Normalize(text, NormalizationForm.FormD) + .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) != UnicodeCategory.NonSpacingMark); + + return Normalize(string.Concat(chars), NormalizationForm.FormC); + } + + private static string Normalize(string text, NormalizationForm form, bool stripStringOnFailure = true) + { + if (stripStringOnFailure) + { + try + { + return text.Normalize(form); + } + catch (ArgumentException) + { + // will throw if input contains invalid unicode chars + // https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/ + text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?The music genre path. string MusicGenrePath { get; } - /// - /// Gets the game genre path. - /// - /// The game genre path. - string GameGenrePath { get; } - /// /// Gets the path to the Studio directory /// diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 9d404ba1a..60c183d04 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -88,13 +88,6 @@ namespace MediaBrowser.Controller.Library /// Task{MusicGenre}. MusicGenre GetMusicGenre(string name); - /// - /// Gets the game genre. - /// - /// The name. - /// Task{GameGenre}. - GameGenre GetGameGenre(string name); - /// /// Gets a Year /// @@ -521,8 +514,6 @@ namespace MediaBrowser.Controller.Library Guid GetMusicGenreId(string name); - Guid GetGameGenreId(string name); - Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary); Task RemoveVirtualFolder(string name, bool refreshLibrary); void AddMediaPath(string virtualFolderName, MediaPathInfo path); @@ -531,7 +522,6 @@ namespace MediaBrowser.Controller.Library QueryResult> GetGenres(InternalItemsQuery query); QueryResult> GetMusicGenres(InternalItemsQuery query); - QueryResult> GetGameGenres(InternalItemsQuery query); QueryResult> GetStudios(InternalItemsQuery query); QueryResult> GetArtists(InternalItemsQuery query); QueryResult> GetAlbumArtists(InternalItemsQuery query); diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs index e2988a831..6b0b7e53a 100644 --- a/MediaBrowser.Controller/Library/NameExtensions.cs +++ b/MediaBrowser.Controller/Library/NameExtensions.cs @@ -1,7 +1,7 @@ using System; +using System.Linq; using System.Collections.Generic; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Extensions; namespace MediaBrowser.Controller.Library { @@ -14,13 +14,11 @@ namespace MediaBrowser.Controller.Library return string.Empty; } - //return name; return name.RemoveDiacritics(); } public static IEnumerable DistinctNames(this IEnumerable names) - { - return names.DistinctBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase); - } + => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase) + .Select(x => x.First()); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs index ba3813d8a..b71a76648 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvService.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvService.cs @@ -12,11 +12,6 @@ namespace MediaBrowser.Controller.LiveTv /// public interface ILiveTvService { - /// - /// Occurs when [data source changed]. - /// - event EventHandler DataSourceChanged; - /// /// Gets the name. /// diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index fc2b8f9c9..f5f147db1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -136,7 +136,7 @@ namespace MediaBrowser.Controller.MediaEncoding return "libtheora"; } - return codec.ToLower(); + return codec.ToLowerInvariant(); } return "copy"; @@ -405,7 +405,7 @@ namespace MediaBrowser.Controller.MediaEncoding return "libopus"; } - return codec.ToLower(); + return codec.ToLowerInvariant(); } /// @@ -762,7 +762,7 @@ namespace MediaBrowser.Controller.MediaEncoding // vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly) if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && - profile != null && profile.ToLower().Contains("baseline")) + profile != null && profile.ToLowerInvariant().Contains("baseline")) { profile = "constrained_baseline"; } @@ -2175,7 +2175,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { - switch (videoStream.Codec.ToLower()) + switch (videoStream.Codec.ToLowerInvariant()) { case "avc": case "h264": @@ -2215,7 +2215,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - switch (videoStream.Codec.ToLower()) + switch (videoStream.Codec.ToLowerInvariant()) { case "avc": case "h264": @@ -2254,7 +2254,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase)) { - switch (videoStream.Codec.ToLower()) + switch (videoStream.Codec.ToLowerInvariant()) { case "avc": case "h264": @@ -2299,7 +2299,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) { - switch (videoStream.Codec.ToLower()) + switch (videoStream.Codec.ToLowerInvariant()) { case "avc": case "h264": @@ -2324,7 +2324,7 @@ namespace MediaBrowser.Controller.MediaEncoding return "-hwaccel dxva2"; } - switch (videoStream.Codec.ToLower()) + switch (videoStream.Codec.ToLowerInvariant()) { case "avc": case "h264": diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1fe8856cc..916d691b8 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -10,19 +10,13 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; -using System.IO; using MediaBrowser.Model.Net; -using MediaBrowser.Controller.Library; -using System.Threading.Tasks; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified public class EncodingJobInfo { - protected readonly IMediaSourceManager MediaSourceManager; - public MediaStream VideoStream { get; set; } public VideoType VideoType { get; set; } public Dictionary RemoteHttpHeaders { get; set; } @@ -49,7 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding public string OutputFilePath { get; set; } public string MimeType { get; set; } - public long? EncodingDurationTicks { get; set; } public string GetMimeType(string outputPath, bool enableStreamDefault = true) { @@ -68,7 +61,12 @@ namespace MediaBrowser.Controller.MediaEncoding { if (_transcodeReasons == null) { - _transcodeReasons = (BaseRequest.TranscodeReasons ?? string.Empty) + if (BaseRequest.TranscodeReasons == null) + { + return Array.Empty(); + } + + _transcodeReasons = BaseRequest.TranscodeReasons .Split(',') .Where(i => !string.IsNullOrEmpty(i)) .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true)) @@ -98,7 +96,8 @@ namespace MediaBrowser.Controller.MediaEncoding get { // For live tv + in progress recordings - if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase)) { if (!MediaSource.RunTimeTicks.HasValue) { @@ -155,15 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (forceDeinterlaceIfSourceIsInterlaced) - { - if (isInputInterlaced) - { - return true; - } - } - - return false; + return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced; } public string[] GetRequestedProfiles(string codec) @@ -211,7 +202,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "maxrefframes"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -230,7 +222,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "videobitdepth"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -249,7 +242,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "audiobitdepth"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -264,6 +258,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return BaseRequest.MaxAudioChannels; } + if (BaseRequest.AudioChannels.HasValue) { return BaseRequest.AudioChannels; @@ -272,7 +267,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "audiochannels"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -294,7 +290,8 @@ namespace MediaBrowser.Controller.MediaEncoding SupportedSubtitleCodecs = Array.Empty(); } - public bool IsSegmentedLiveStream => TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue; + public bool IsSegmentedLiveStream + => TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue; public bool EnableBreakOnNonKeyFrames(string videoCodec) { @@ -428,11 +425,12 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.Level; + return VideoStream?.Level; } var level = GetRequestedLevel(ActualOutputVideoCodec); - if (!string.IsNullOrEmpty(level) && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(level) + && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -450,7 +448,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.BitDepth; + return VideoStream?.BitDepth; } return null; @@ -467,7 +465,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.RefFrames; + return VideoStream?.RefFrames; } return null; @@ -494,13 +492,14 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? + if (BaseRequest.Static) + { + return InputTimestamp; + } + + return string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? TransportStreamTimestamp.Valid : TransportStreamTimestamp.None; - - return !BaseRequest.Static - ? defaultValue - : InputTimestamp; } } @@ -513,7 +512,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.PacketLength; + return VideoStream?.PacketLength; } return null; @@ -529,7 +528,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.Profile; + return VideoStream?.Profile; } var requestedProfile = GetRequestedProfiles(ActualOutputVideoCodec).FirstOrDefault(); @@ -542,42 +541,13 @@ namespace MediaBrowser.Controller.MediaEncoding } } - /// - /// Predicts the audio sample rate that will be in the output stream - /// - public string TargetVideoRange - { - get - { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return VideoStream == null ? null : VideoStream.VideoRange; - } - - return "SDR"; - } - } - - public string TargetAudioProfile - { - get - { - if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return AudioStream == null ? null : AudioStream.Profile; - } - - return null; - } - } - public string TargetVideoCodecTag { get { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.CodecTag; + return VideoStream?.CodecTag; } return null; @@ -590,7 +560,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.IsAnamorphic; + return VideoStream?.IsAnamorphic; } return false; @@ -605,14 +575,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; + return VideoStream?.Codec; } return codec; @@ -627,14 +590,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; + return AudioStream?.Codec; } return codec; @@ -647,7 +603,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced; + return VideoStream?.IsInterlaced; } if (DeInterlace(ActualOutputVideoCodec, true)) @@ -655,7 +611,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced; + return VideoStream?.IsInterlaced; } } @@ -665,7 +621,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.IsAVC; + return VideoStream?.IsAVC; } return false; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 48055a37e..057e43910 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -82,28 +82,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string GetTimeParameter(long ticks); - /// - /// Encodes the audio. - /// - /// The options. - /// The progress. - /// The cancellation token. - /// Task. - Task EncodeAudio(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken); - - /// - /// Encodes the video. - /// - /// The options. - /// The progress. - /// The cancellation token. - /// Task<System.String>. - Task EncodeVideo(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken); - Task ConvertImage(string inputPath, string outputPath); /// diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 4e7e1c8ed..4242a00e2 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -6,7 +6,6 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Net @@ -23,8 +22,8 @@ namespace MediaBrowser.Controller.Net /// /// The _active connections /// - protected readonly List> ActiveConnections = - new List>(); + protected readonly List> ActiveConnections = + new List>(); /// /// Gets the name. @@ -44,8 +43,6 @@ namespace MediaBrowser.Controller.Net /// protected ILogger Logger; - protected ITimerFactory TimerFactory { get; private set; } - protected BasePeriodicWebSocketListener(ILogger logger) { if (logger == null) @@ -111,7 +108,7 @@ namespace MediaBrowser.Controller.Net Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); var timer = SendOnTimer ? - TimerFactory.Create(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) : + new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) : null; var state = new TStateType @@ -122,7 +119,7 @@ namespace MediaBrowser.Controller.Net lock (ActiveConnections) { - ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state)); + ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state)); } if (timer != null) @@ -139,7 +136,7 @@ namespace MediaBrowser.Controller.Net { var connection = (IWebSocketConnection)state; - Tuple tuple; + Tuple tuple; lock (ActiveConnections) { @@ -162,7 +159,7 @@ namespace MediaBrowser.Controller.Net protected void SendData(bool force) { - Tuple[] tuples; + Tuple[] tuples; lock (ActiveConnections) { @@ -190,7 +187,7 @@ namespace MediaBrowser.Controller.Net } } - private async void SendData(Tuple tuple) + private async void SendData(Tuple tuple) { var connection = tuple.Item1; @@ -249,7 +246,7 @@ namespace MediaBrowser.Controller.Net /// Disposes the connection. /// /// The connection. - private void DisposeConnection(Tuple connection) + private void DisposeConnection(Tuple connection) { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 0d086fd7e..5156fce11 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -143,13 +143,11 @@ namespace MediaBrowser.Controller.Persistence QueryResult> GetGenres(InternalItemsQuery query); QueryResult> GetMusicGenres(InternalItemsQuery query); - QueryResult> GetGameGenres(InternalItemsQuery query); QueryResult> GetStudios(InternalItemsQuery query); QueryResult> GetArtists(InternalItemsQuery query); QueryResult> GetAlbumArtists(InternalItemsQuery query); QueryResult> GetAllArtists(InternalItemsQuery query); - List GetGameGenreNames(); List GetMusicGenreNames(); List GetStudioNames(); List GetGenreNames(); diff --git a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs index 7b7a591aa..e57929989 100644 --- a/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs +++ b/MediaBrowser.Controller/Plugins/IServerEntryPoint.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Plugins { @@ -10,7 +11,7 @@ namespace MediaBrowser.Controller.Plugins /// /// Runs this instance. /// - void Run(); + Task RunAsync(); } public interface IRunBeforeStartup diff --git a/MediaBrowser.Controller/Providers/GameInfo.cs b/MediaBrowser.Controller/Providers/GameInfo.cs deleted file mode 100644 index 1f3eb40b7..000000000 --- a/MediaBrowser.Controller/Providers/GameInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public class GameInfo : ItemLookupInfo - { - /// - /// Gets or sets the game system. - /// - /// The game system. - public string GameSystem { get; set; } - } -} diff --git a/MediaBrowser.Controller/Providers/GameSystemInfo.cs b/MediaBrowser.Controller/Providers/GameSystemInfo.cs deleted file mode 100644 index 796486b82..000000000 --- a/MediaBrowser.Controller/Providers/GameSystemInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Controller.Providers -{ - public class GameSystemInfo : ItemLookupInfo - { - /// - /// Gets or sets the path. - /// - /// The path. - public string Path { get; set; } - } -} diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index d698795dd..f0e81e8e7 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,10 +1,10 @@ using System; using System.Linq; +using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; -using MediaBrowser.Model.Threading; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Session @@ -268,10 +268,10 @@ namespace MediaBrowser.Controller.Session } private readonly object _progressLock = new object(); - private ITimer _progressTimer; + private Timer _progressTimer; private PlaybackProgressInfo _lastProgressInfo; - public void StartAutomaticProgress(ITimerFactory timerFactory, PlaybackProgressInfo progressInfo) + public void StartAutomaticProgress(PlaybackProgressInfo progressInfo) { if (_disposed) { @@ -284,7 +284,7 @@ namespace MediaBrowser.Controller.Session if (_progressTimer == null) { - _progressTimer = timerFactory.Create(OnProgressTimerCallback, null, 1000, 1000); + _progressTimer = new Timer(OnProgressTimerCallback, null, 1000, 1000); } else { diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index 0a4928ed7..1a7654bfd 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -128,7 +128,6 @@ namespace MediaBrowser.LocalMetadata.Images var added = false; var isEpisode = item is Episode; var isSong = item.GetType() == typeof(Audio); - var isGame = item is Game; var isPerson = item is Person; // Logo @@ -157,7 +156,7 @@ namespace MediaBrowser.LocalMetadata.Images added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc); } } - else if (isGame || item is Video || item is BoxSet) + else if (item is Video || item is BoxSet) { added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc); @@ -172,19 +171,6 @@ namespace MediaBrowser.LocalMetadata.Images } } - if (isGame) - { - AddImage(files, images, "box", imagePrefix, isInMixedFolder, ImageType.Box); - AddImage(files, images, "menu", imagePrefix, isInMixedFolder, ImageType.Menu); - - added = AddImage(files, images, "back", imagePrefix, isInMixedFolder, ImageType.BoxRear); - - if (!added) - { - added = AddImage(files, images, "boxrear", imagePrefix, isInMixedFolder, ImageType.BoxRear); - } - } - // Banner if (!isEpisode && !isSong && !isPerson) { @@ -417,7 +403,7 @@ namespace MediaBrowser.LocalMetadata.Images var seriesFiles = GetFiles(series, false, directoryService).ToList(); // Try using the season name - var prefix = season.Name.ToLower().Replace(" ", string.Empty); + var prefix = season.Name.ToLowerInvariant().Replace(" ", string.Empty); var filenamePrefixes = new List { prefix }; diff --git a/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs deleted file mode 100644 index a4997270f..000000000 --- a/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Parsers -{ - public class GameSystemXmlParser : BaseItemXmlParser - { - public Task FetchAsync(MetadataResult item, string metadataFile, CancellationToken cancellationToken) - { - Fetch(item, metadataFile, cancellationToken); - - cancellationToken.ThrowIfCancellationRequested(); - - return Task.CompletedTask; - } - - /// - /// Fetches the data from XML node. - /// - /// The reader. - /// The result. - protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult result) - { - var item = result.Item; - - switch (reader.Name) - { - case "GameSystem": - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - item.GameSystemName = val; - } - break; - } - - case "GamesDbId": - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - item.SetProviderId(MetadataProviders.Gamesdb, val); - } - break; - } - - - default: - base.FetchDataFromXmlNode(reader, result); - break; - } - } - - public GameSystemXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) - { - } - } -} diff --git a/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs deleted file mode 100644 index df7c51f27..000000000 --- a/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Parsers -{ - /// - /// Class EpisodeXmlParser - /// - public class GameXmlParser : BaseItemXmlParser - { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public Task FetchAsync(MetadataResult item, string metadataFile, CancellationToken cancellationToken) - { - Fetch(item, metadataFile, cancellationToken); - - cancellationToken.ThrowIfCancellationRequested(); - - return Task.CompletedTask; - } - - /// - /// Fetches the data from XML node. - /// - /// The reader. - /// The result. - protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult result) - { - var item = result.Item; - - switch (reader.Name) - { - case "GameSystem": - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - item.GameSystem = val; - } - break; - } - - case "GamesDbId": - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - item.SetProviderId(MetadataProviders.Gamesdb, val); - } - break; - } - - case "Players": - { - var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) - { - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var num)) - { - item.PlayersSupported = num; - } - } - break; - } - - - default: - base.FetchDataFromXmlNode(reader, result); - break; - } - } - - public GameXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) - { - } - } -} diff --git a/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs deleted file mode 100644 index 62f31d4fa..000000000 --- a/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.LocalMetadata.Parsers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Providers -{ - public class GameSystemXmlProvider : BaseXmlProvider - { - private readonly ILogger _logger; - private readonly IProviderManager _providerManager; - private readonly IXmlReaderSettingsFactory _xmlSettings; - - public GameSystemXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlSettings) - : base(fileSystem) - { - _logger = logger; - _providerManager = providerManager; - _xmlSettings = xmlSettings; - } - - protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) - { - new GameSystemXmlParser(_logger, _providerManager, _xmlSettings, FileSystem).Fetch(result, path, cancellationToken); - } - - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) - { - return directoryService.GetFile(Path.Combine(info.Path, "gamesystem.xml")); - } - } -} diff --git a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs deleted file mode 100644 index acdbb0a29..000000000 --- a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.IO; -using System.Threading; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Providers; -using MediaBrowser.LocalMetadata.Parsers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Providers -{ - public class GameXmlProvider : BaseXmlProvider - { - private readonly ILogger _logger; - private readonly IProviderManager _providerManager; - private readonly IXmlReaderSettingsFactory _xmlSettings; - - public GameXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlSettings) - : base(fileSystem) - { - _logger = logger; - _providerManager = providerManager; - _xmlSettings = xmlSettings; - } - - protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) - { - new GameXmlParser(_logger, _providerManager, _xmlSettings, FileSystem).Fetch(result, path, cancellationToken); - } - - protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) - { - var specificFile = Path.ChangeExtension(info.Path, ".xml"); - var file = FileSystem.GetFileInfo(specificFile); - - return info.IsInMixedFolder || file.Exists ? file : FileSystem.GetFileInfo(Path.Combine(Path.GetDirectoryName(info.Path), "game.xml")); - } - } -} diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 8a291e6a8..438b84252 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -172,7 +172,7 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteElementString("Added", item.DateCreated.ToLocalTime().ToString("G")); - writer.WriteElementString("LockData", item.IsLocked.ToString().ToLower()); + writer.WriteElementString("LockData", item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); if (item.LockedFields.Length > 0) { @@ -410,7 +410,9 @@ namespace MediaBrowser.LocalMetadata.Savers writer.WriteStartElement("Share"); writer.WriteElementString("UserId", share.UserId); - writer.WriteElementString("CanEdit", share.CanEdit.ToString().ToLower()); + writer.WriteElementString( + "CanEdit", + share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); writer.WriteEndElement(); } diff --git a/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs deleted file mode 100644 index cf123171a..000000000 --- a/MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.IO; -using System.Xml; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Savers -{ - public class GameSystemXmlSaver : BaseXmlSaver - { - public GameSystemXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) - { - } - - public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) - { - if (!item.SupportsLocalMetadata) - { - return false; - } - - return item is GameSystem && updateType >= ItemUpdateType.MetadataDownload; - } - - protected override void WriteCustomElements(BaseItem item, XmlWriter writer) - { - var gameSystem = (GameSystem)item; - - if (!string.IsNullOrEmpty(gameSystem.GameSystemName)) - { - writer.WriteElementString("GameSystem", gameSystem.GameSystemName); - } - } - - protected override string GetLocalSavePath(BaseItem item) - { - return Path.Combine(item.Path, "gamesystem.xml"); - } - - protected override string GetRootElementName(BaseItem item) - { - return "Item"; - } - } -} diff --git a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs deleted file mode 100644 index 3b7929618..000000000 --- a/MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Globalization; -using System.IO; -using System.Xml; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.LocalMetadata.Savers -{ - /// - /// Saves game.xml for games - /// - public class GameXmlSaver : BaseXmlSaver - { - private readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public override bool IsEnabledFor(BaseItem item, ItemUpdateType updateType) - { - if (!item.SupportsLocalMetadata) - { - return false; - } - - return item is Game && updateType >= ItemUpdateType.MetadataDownload; - } - - protected override void WriteCustomElements(BaseItem item, XmlWriter writer) - { - var game = (Game)item; - - if (!string.IsNullOrEmpty(game.GameSystem)) - { - writer.WriteElementString("GameSystem", game.GameSystem); - } - if (game.PlayersSupported.HasValue) - { - writer.WriteElementString("Players", game.PlayersSupported.Value.ToString(UsCulture)); - } - } - - protected override string GetLocalSavePath(BaseItem item) - { - return GetGameSavePath((Game)item); - } - - protected override string GetRootElementName(BaseItem item) - { - return "Item"; - } - - public static string GetGameSavePath(Game item) - { - if (item.IsInMixedFolder) - { - return Path.ChangeExtension(item.Path, ".xml"); - } - - return Path.Combine(item.ContainingFolderPath, "game.xml"); - } - - public GameXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) - { - } - } -} diff --git a/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs deleted file mode 100644 index 7dd2c589f..000000000 --- a/MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace MediaBrowser.LocalMetadata.Savers -{ - ///// - ///// Class PersonXmlSaver - ///// - //public class PersonXmlSaver : BaseXmlSaver - //{ - // public override bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType) - // { - // if (!item.SupportsLocalMetadata) - // { - // return false; - // } - - // return item is Person && updateType >= ItemUpdateType.MetadataDownload; - // } - - // protected override List GetTagsUsed() - // { - // var list = new List - // { - // "PlaceOfBirth" - // }; - - // return list; - // } - - // protected override void WriteCustomElements(IHasMetadata item, XmlWriter writer) - // { - // var person = (Person)item; - - // if (person.ProductionLocations.Count > 0) - // { - // writer.WriteElementString("PlaceOfBirth", person.ProductionLocations[0]); - // } - // } - - // protected override string GetLocalSavePath(IHasMetadata item) - // { - // return Path.Combine(item.Path, "person.xml"); - // } - - // public PersonXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) - // { - // } - //} -} diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs deleted file mode 100644 index d5773fe31..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class AudioEncoder : BaseEncoder - { - public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, state.OutputFilePath); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var audioCodec = state.Options.AudioCodec; - - if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".aac"; - } - if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".mp3"; - } - if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".ogg"; - } - if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase)) - { - return ".wma"; - } - - return null; - } - - protected override bool IsVideoEncoder => false; - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs deleted file mode 100644 index d3c44f5eb..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ /dev/null @@ -1,372 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public abstract class BaseEncoder - { - protected readonly MediaEncoder MediaEncoder; - protected readonly ILogger Logger; - protected readonly IServerConfigurationManager ConfigurationManager; - protected readonly IFileSystem FileSystem; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly ISessionManager SessionManager; - protected readonly ISubtitleEncoder SubtitleEncoder; - protected readonly IMediaSourceManager MediaSourceManager; - protected IProcessFactory ProcessFactory; - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected EncodingHelper EncodingHelper; - - protected BaseEncoder(MediaEncoder mediaEncoder, - ILogger logger, - IServerConfigurationManager configurationManager, - IFileSystem fileSystem, - IIsoManager isoManager, - ILibraryManager libraryManager, - ISessionManager sessionManager, - ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) - { - MediaEncoder = mediaEncoder; - Logger = logger; - ConfigurationManager = configurationManager; - FileSystem = fileSystem; - IsoManager = isoManager; - LibraryManager = libraryManager; - SessionManager = sessionManager; - SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - ProcessFactory = processFactory; - - EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); - } - - public async Task Start(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken) - { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder) - .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); - - encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); - Directory.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath)); - - encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate; - - await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); - - var commandLineArgs = GetCommandLineArguments(encodingJob); - - var process = ProcessFactory.Create(new ProcessOptions - { - CreateNoWindow = true, - UseShellExecute = false, - - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, - - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, - - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true - }); - - var workingDirectory = GetWorkingDirectory(options); - if (!string.IsNullOrWhiteSpace(workingDirectory)) - { - process.StartInfo.WorkingDirectory = workingDirectory; - } - - OnTranscodeBeginning(encodingJob); - - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - Logger.LogInformation(commandLineLogMessage); - - var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt"); - Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); - - // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. - encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true); - - var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false); - - process.Exited += (sender, args) => OnFfMpegProcessExited(process, encodingJob); - - try - { - process.Start(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error starting ffmpeg"); - - OnTranscodeFailedToStart(encodingJob.OutputFilePath, encodingJob); - - throw; - } - - cancellationToken.Register(() => Cancel(process, encodingJob)); - - // MUST read both stdout and stderr asynchronously or a deadlock may occurr - //process.BeginOutputReadLine(); - - // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream); - - // Wait for the file to exist before proceeeding - while (!File.Exists(encodingJob.OutputFilePath) && !encodingJob.HasExited) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } - - return encodingJob; - } - - private void Cancel(IProcess process, EncodingJob job) - { - Logger.LogInformation("Killing ffmpeg process for {0}", job.OutputFilePath); - - //process.Kill(); - process.StandardInput.WriteLine("q"); - - job.IsCancelled = true; - } - - /// - /// Processes the exited. - /// - /// The process. - /// The job. - private void OnFfMpegProcessExited(IProcess process, EncodingJob job) - { - job.HasExited = true; - - Logger.LogDebug("Disposing stream resources"); - job.Dispose(); - - var isSuccesful = false; - - try - { - var exitCode = process.ExitCode; - Logger.LogInformation("FFMpeg exited with code {0}", exitCode); - - isSuccesful = exitCode == 0; - } - catch (Exception ex) - { - Logger.LogError(ex, "FFMpeg exited with an error."); - } - - if (isSuccesful && !job.IsCancelled) - { - job.TaskCompletionSource.TrySetResult(true); - } - else if (job.IsCancelled) - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new OperationCanceledException()); - } - catch - { - } - } - else - { - try - { - DeleteFiles(job); - } - catch - { - } - try - { - job.TaskCompletionSource.TrySetException(new Exception("Encoding failed")); - } - catch - { - } - } - - // This causes on exited to be called twice: - //try - //{ - // // Dispose the process - // process.Dispose(); - //} - //catch (Exception ex) - //{ - // Logger.LogError("Error disposing ffmpeg.", ex); - //} - } - - protected virtual void DeleteFiles(EncodingJob job) - { - FileSystem.DeleteFile(job.OutputFilePath); - } - - private void OnTranscodeBeginning(EncodingJob job) - { - job.ReportTranscodingProgress(null, null, null, null, null); - } - - private void OnTranscodeFailedToStart(string path, EncodingJob job) - { - if (!string.IsNullOrWhiteSpace(job.Options.DeviceId)) - { - SessionManager.ClearTranscodingInfo(job.Options.DeviceId); - } - } - - protected abstract bool IsVideoEncoder { get; } - - protected virtual string GetWorkingDirectory(EncodingJobOptions options) - { - return null; - } - - protected EncodingOptions GetEncodingOptions() - { - return ConfigurationManager.GetConfiguration("encoding"); - } - - protected abstract string GetCommandLineArguments(EncodingJob job); - - private string GetOutputFilePath(EncodingJob state) - { - var folder = string.IsNullOrWhiteSpace(state.Options.TempDirectory) ? - ConfigurationManager.ApplicationPaths.TranscodingTempPath : - state.Options.TempDirectory; - - var outputFileExtension = GetOutputFileExtension(state); - - var filename = state.Id + (outputFileExtension ?? string.Empty).ToLower(); - return Path.Combine(folder, filename); - } - - protected virtual string GetOutputFileExtension(EncodingJob state) - { - if (!string.IsNullOrWhiteSpace(state.Options.Container)) - { - return "." + state.Options.Container; - } - - return null; - } - - /// - /// Gets the name of the output video codec - /// - /// The state. - /// System.String. - protected string GetVideoDecoder(EncodingJob state) - { - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - // Only use alternative encoders for video files. - // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully - // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. - if (state.VideoType != VideoType.VideoFile) - { - return null; - } - - if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec)) - { - if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) - { - switch (state.MediaSource.VideoStream.Codec.ToLower()) - { - case "avc": - case "h264": - if (MediaEncoder.SupportsDecoder("h264_qsv")) - { - // Seeing stalls and failures with decoding. Not worth it compared to encoding. - return "-c:v h264_qsv "; - } - break; - case "mpeg2video": - if (MediaEncoder.SupportsDecoder("mpeg2_qsv")) - { - return "-c:v mpeg2_qsv "; - } - break; - case "vc1": - if (MediaEncoder.SupportsDecoder("vc1_qsv")) - { - return "-c:v vc1_qsv "; - } - break; - } - } - } - - // leave blank so ffmpeg will decide - return null; - } - - private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken) - { - if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath)) - { - state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false); - } - - if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Options.LiveStreamId)) - { - var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest - { - OpenToken = state.MediaSource.OpenToken - - }, cancellationToken).ConfigureAwait(false); - - EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null); - - if (state.IsVideoRequest) - { - EncodingHelper.TryStreamCopy(state); - } - } - - if (state.MediaSource.BufferMs.HasValue) - { - await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false); - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs deleted file mode 100644 index d4040cd31..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJob : EncodingJobInfo, IDisposable - { - public bool HasExited { get; internal set; } - public bool IsCancelled { get; internal set; } - - public Stream LogFileStream { get; set; } - public TaskCompletionSource TaskCompletionSource; - - public EncodingJobOptions Options - { - get => (EncodingJobOptions)BaseRequest; - set => BaseRequest = value; - } - - public Guid Id { get; set; } - - public bool EstimateContentLength { get; set; } - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - - public string ItemType { get; set; } - - private readonly ILogger _logger; - private readonly IMediaSourceManager _mediaSourceManager; - - public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) : - base(TranscodingJobType.Progressive) - { - _logger = logger; - _mediaSourceManager = mediaSourceManager; - Id = Guid.NewGuid(); - - _logger = logger; - TaskCompletionSource = new TaskCompletionSource(); - } - - public override void Dispose() - { - DisposeLiveStream(); - DisposeLogStream(); - DisposeIsoMount(); - } - - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing log stream"); - } - - LogFileStream = null; - } - } - - private async void DisposeLiveStream() - { - if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Options.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) - { - try - { - await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing media source"); - } - } - } - - - private void DisposeIsoMount() - { - if (IsoMount != null) - { - try - { - IsoMount.Dispose(); - } - catch (Exception ex) - { - _logger.LogError("Error disposing iso mount", ex); - } - - IsoMount = null; - } - } - - public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) - { - var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; - - //job.Framerate = framerate; - - if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) - { - var pct = ticks.Value / RunTimeTicks.Value; - percentComplete = pct * 100; - } - - if (percentComplete.HasValue) - { - Progress.Report(percentComplete.Value); - } - - /* - job.TranscodingPositionTicks = ticks; - job.BytesTranscoded = bytesTranscoded; - - var deviceId = Options.DeviceId; - - if (!string.IsNullOrWhiteSpace(deviceId)) - { - var audioCodec = ActualOutputVideoCodec; - var videoCodec = ActualOutputVideoCodec; - - SessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo - { - Bitrate = job.TotalOutputBitrate, - AudioCodec = audioCodec, - VideoCodec = videoCodec, - Container = job.Options.OutputContainer, - Framerate = framerate, - CompletionPercentage = percentComplete, - Width = job.OutputWidth, - Height = job.OutputHeight, - AudioChannels = job.OutputAudioChannels, - IsAudioDirect = string.Equals(job.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), - IsVideoDirect = string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) - }); - }*/ - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs deleted file mode 100644 index 95454c447..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJobFactory - { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IConfigurationManager _config; - private readonly IMediaEncoder _mediaEncoder; - - protected static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder) - { - _logger = logger; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - _config = config; - _mediaEncoder = mediaEncoder; - } - - public async Task CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress progress, CancellationToken cancellationToken) - { - var request = options; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = InferAudioCodec(request.Container); - } - - var state = new EncodingJob(_logger, _mediaSourceManager) - { - Options = options, - IsVideoRequest = isVideoRequest, - Progress = progress - }; - - if (!string.IsNullOrWhiteSpace(request.VideoCodec)) - { - state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = _libraryManager.GetItemById(request.Id); - state.ItemType = item.GetType().Name; - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - // TODO - // var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? - // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); - - // if (primaryImage != null) - // { - // state.AlbumCoverPath = primaryImage.Path; - // } - - // TODO network path substition useful ? - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, true, cancellationToken).ConfigureAwait(false); - - var mediaSource = string.IsNullOrEmpty(request.MediaSourceId) - ? mediaSources.First() - : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId)); - - var videoRequest = state.Options; - - encodingHelper.AttachMediaSourceInfo(state, mediaSource, null); - - //var container = Path.GetExtension(state.RequestedUrl); - - //if (string.IsNullOrEmpty(container)) - //{ - // container = request.Static ? - // state.InputContainer : - // (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.'); - //} - - //state.OutputContainer = (container ?? string.Empty).TrimStart('.'); - - state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream); - - state.OutputAudioCodec = state.Options.AudioCodec; - - state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); - - if (videoRequest != null) - { - state.OutputVideoCodec = state.Options.VideoCodec; - state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec); - - if (state.OutputVideoBitrate.HasValue) - { - var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.VideoStream == null ? (int?)null : state.VideoStream.Width, - state.VideoStream == null ? (int?)null : state.VideoStream.Height, - state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, - state.OutputVideoCodec, - videoRequest.MaxWidth, - videoRequest.MaxHeight); - - videoRequest.MaxWidth = resolution.MaxWidth; - videoRequest.MaxHeight = resolution.MaxHeight; - } - } - - ApplyDeviceProfileSettings(state); - - if (videoRequest != null) - { - encodingHelper.TryStreamCopy(state); - } - - //state.OutputFilePath = GetOutputFilePath(state); - - return state; - } - - protected EncodingOptions GetEncodingOptions() - { - return _config.GetConfiguration("encoding"); - } - - /// - /// Infers the video codec. - /// - /// The container. - /// System.Nullable{VideoCodecs}. - private static string InferVideoCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) - { - return "wmv"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vpx"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "theora"; - } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) - { - return "h264"; - } - - return "copy"; - } - - private string InferAudioCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - return "mp3"; - } - if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac"; - } - if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) - { - return "wma"; - } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - - return "copy"; - } - - /// - /// Determines whether the specified stream is H264. - /// - /// The stream. - /// true if the specified stream is H264; otherwise, false. - protected bool IsH264(MediaStream stream) - { - var codec = stream.Codec ?? string.Empty; - - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; - } - - private static int GetVideoProfileScore(string profile) - { - var list = new List - { - "Constrained Baseline", - "Baseline", - "Extended", - "Main", - "High", - "Progressive High", - "Constrained High" - }; - - return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); - } - - private void ApplyDeviceProfileSettings(EncodingJob state) - { - var profile = state.Options.DeviceProfile; - - if (profile == null) - { - // Don't use settings from the default profile. - // Only use a specific profile if it was requested. - return; - } - - var audioCodec = state.ActualOutputAudioCodec; - - var videoCodec = state.ActualOutputVideoCodec; - var outputContainer = state.Options.Container; - - var mediaProfile = state.IsVideoRequest ? - profile.GetAudioMediaProfile(outputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) : - profile.GetVideoMediaProfile(outputContainer, - audioCodec, - videoCodec, - state.OutputWidth, - state.OutputHeight, - state.TargetVideoBitDepth, - state.OutputVideoBitrate, - state.TargetVideoProfile, - state.TargetVideoLevel, - state.TargetFramerate, - state.TargetPacketLength, - state.TargetTimestamp, - state.IsTargetAnamorphic, - state.IsTargetInterlaced, - state.TargetRefFrames, - state.TargetVideoStreamCount, - state.TargetAudioStreamCount, - state.TargetVideoCodecTag, - state.IsTargetAVC); - - if (mediaProfile != null) - { - state.MimeType = mediaProfile.MimeType; - } - - var transcodingProfile = state.IsVideoRequest ? - profile.GetAudioTranscodingProfile(outputContainer, audioCodec) : - profile.GetVideoTranscodingProfile(outputContainer, audioCodec, videoCodec); - - if (transcodingProfile != null) - { - state.EstimateContentLength = transcodingProfile.EstimateContentLength; - //state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; - state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo; - - state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps; - } - } - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 54344424d..d922f1068 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -878,49 +878,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - public async Task EncodeAudio(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken) - { - var job = await new AudioEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - - public async Task EncodeVideo(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken) - { - _logger.LogError("EncodeVideo"); - var job = await new VideoEncoder(this, - _logger, - ConfigurationManager, - FileSystem, - IsoManager, - LibraryManager, - SessionManager, - SubtitleEncoder(), - MediaSourceManager(), - _processFactory) - .Start(options, progress, cancellationToken).ConfigureAwait(false); - - await job.TaskCompletionSource.Task.ConfigureAwait(false); - - return job.OutputFilePath; - } - private void StartProcess(ProcessWrapper process) { process.Process.Start(); diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs deleted file mode 100644 index bf23a73bd..000000000 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class VideoEncoder : BaseEncoder - { - public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory) - { - } - - protected override string GetCommandLineArguments(EncodingJob state) - { - // Get the output codec name - var encodingOptions = GetEncodingOptions(); - - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast"); - } - - protected override string GetOutputFileExtension(EncodingJob state) - { - var ext = base.GetOutputFileExtension(state); - - if (!string.IsNullOrEmpty(ext)) - { - return ext; - } - - var videoCodec = state.Options.VideoCodec; - - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) - { - return ".ts"; - } - if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase)) - { - return ".ogv"; - } - if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase)) - { - return ".webm"; - } - if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase)) - { - return ".asf"; - } - - return null; - } - - protected override bool IsVideoEncoder => true; - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs index 2c18a02ef..0d696b906 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SsaParser.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (!eventsStarted) header.AppendLine(line); - if (line.Trim().ToLower() == "[events]") + if (line.Trim().ToLowerInvariant() == "[events]") { eventsStarted = true; } @@ -54,25 +54,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles } else if (eventsStarted && line.Trim().Length > 0) { - string s = line.Trim().ToLower(); + string s = line.Trim().ToLowerInvariant(); if (s.StartsWith("format:")) { if (line.Length > 10) { - format = line.ToLower().Substring(8).Split(','); + format = line.ToLowerInvariant().Substring(8).Split(','); for (int i = 0; i < format.Length; i++) { - if (format[i].Trim().ToLower() == "layer") + if (format[i].Trim().ToLowerInvariant() == "layer") indexLayer = i; - else if (format[i].Trim().ToLower() == "start") + else if (format[i].Trim().ToLowerInvariant() == "start") indexStart = i; - else if (format[i].Trim().ToLower() == "end") + else if (format[i].Trim().ToLowerInvariant() == "end") indexEnd = i; - else if (format[i].Trim().ToLower() == "text") + else if (format[i].Trim().ToLowerInvariant() == "text") indexText = i; - else if (format[i].Trim().ToLower() == "effect") + else if (format[i].Trim().ToLowerInvariant() == "effect") indexEffect = i; - else if (format[i].Trim().ToLower() == "style") + else if (format[i].Trim().ToLowerInvariant() == "style") indexStyle = i; } } @@ -222,7 +222,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); + color = color.ToLowerInvariant(); text = text.Remove(start, end - start + 1); if (italic) @@ -252,7 +252,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); + color = color.ToLowerInvariant(); text = text.Remove(start, end - start + 1); if (italic) @@ -367,7 +367,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles color = color.PadLeft(6, '0'); // switch to rrggbb from bbggrr color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2); - color = color.ToLower(); + color = color.ToLowerInvariant(); extraTags += " color=\"" + color + "\""; } diff --git a/MediaBrowser.Model/Channels/ChannelMediaContentType.cs b/MediaBrowser.Model/Channels/ChannelMediaContentType.cs index 010ff8048..fc7c21706 100644 --- a/MediaBrowser.Model/Channels/ChannelMediaContentType.cs +++ b/MediaBrowser.Model/Channels/ChannelMediaContentType.cs @@ -16,8 +16,6 @@ namespace MediaBrowser.Model.Channels MovieExtra = 6, - TvExtra = 7, - - GameExtra = 8 + TvExtra = 7 } } diff --git a/MediaBrowser.Model/Configuration/UnratedItem.cs b/MediaBrowser.Model/Configuration/UnratedItem.cs index 2d0bce47f..107b4e520 100644 --- a/MediaBrowser.Model/Configuration/UnratedItem.cs +++ b/MediaBrowser.Model/Configuration/UnratedItem.cs @@ -6,7 +6,6 @@ namespace MediaBrowser.Model.Configuration Trailer, Series, Music, - Game, Book, LiveTvChannel, LiveTvProgram, diff --git a/MediaBrowser.Model/Connect/ConnectAuthorization.cs b/MediaBrowser.Model/Connect/ConnectAuthorization.cs deleted file mode 100644 index cdb3172d9..000000000 --- a/MediaBrowser.Model/Connect/ConnectAuthorization.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Connect -{ - public class ConnectAuthorization - { - public string ConnectUserId { get; set; } - public string UserName { get; set; } - public string ImageUrl { get; set; } - public string Id { get; set; } - public string[] EnabledLibraries { get; set; } - public bool EnableLiveTv { get; set; } - public string[] EnabledChannels { get; set; } - - public ConnectAuthorization() - { - EnabledLibraries = Array.Empty(); - EnabledChannels = Array.Empty(); - } - } -} diff --git a/MediaBrowser.Model/Connect/ConnectUser.cs b/MediaBrowser.Model/Connect/ConnectUser.cs deleted file mode 100644 index 4c536c6b0..000000000 --- a/MediaBrowser.Model/Connect/ConnectUser.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MediaBrowser.Model.Connect -{ - public class ConnectUser - { - public string Id { get; set; } - public string Name { get; set; } - public string Email { get; set; } - public bool IsActive { get; set; } - public string ImageUrl { get; set; } - } -} diff --git a/MediaBrowser.Model/Connect/ConnectUserQuery.cs b/MediaBrowser.Model/Connect/ConnectUserQuery.cs deleted file mode 100644 index 4f04934d6..000000000 --- a/MediaBrowser.Model/Connect/ConnectUserQuery.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MediaBrowser.Model.Connect -{ - public class ConnectUserQuery - { - public string Id { get; set; } - public string Name { get; set; } - public string Email { get; set; } - public string NameOrEmail { get; set; } - } -} diff --git a/MediaBrowser.Model/Connect/UserLinkType.cs b/MediaBrowser.Model/Connect/UserLinkType.cs deleted file mode 100644 index 19b4b67e6..000000000 --- a/MediaBrowser.Model/Connect/UserLinkType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace MediaBrowser.Model.Connect -{ - public enum UserLinkType - { - /// - /// The linked user - /// - LinkedUser = 0, - /// - /// The guest - /// - Guest = 1 - } -} diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 6d03a03b0..10efb9b38 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -223,7 +223,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("DeviceProfileId", item.DeviceProfileId ?? string.Empty)); list.Add(new NameValuePair("DeviceId", item.DeviceId ?? string.Empty)); list.Add(new NameValuePair("MediaSourceId", item.MediaSourceId ?? string.Empty)); - list.Add(new NameValuePair("Static", item.IsDirectStream.ToString().ToLower())); + list.Add(new NameValuePair("Static", item.IsDirectStream.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); list.Add(new NameValuePair("VideoCodec", videoCodecs)); list.Add(new NameValuePair("AudioCodec", audioCodecs)); list.Add(new NameValuePair("AudioStreamIndex", item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); @@ -251,7 +251,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("PlaySessionId", item.PlaySessionId ?? string.Empty)); list.Add(new NameValuePair("api_key", accessToken ?? string.Empty)); - string liveStreamId = item.MediaSource == null ? null : item.MediaSource.LiveStreamId; + string liveStreamId = item.MediaSource?.LiveStreamId; list.Add(new NameValuePair("LiveStreamId", liveStreamId ?? string.Empty)); list.Add(new NameValuePair("SubtitleMethod", item.SubtitleStreamIndex.HasValue && item.SubtitleDeliveryMethod != SubtitleDeliveryMethod.External ? item.SubtitleDeliveryMethod.ToString() : string.Empty)); @@ -261,37 +261,37 @@ namespace MediaBrowser.Model.Dlna { if (item.RequireNonAnamorphic) { - list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString().ToLower())); + list.Add(new NameValuePair("RequireNonAnamorphic", item.RequireNonAnamorphic.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } list.Add(new NameValuePair("TranscodingMaxAudioChannels", item.TranscodingMaxAudioChannels.HasValue ? item.TranscodingMaxAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : string.Empty)); if (item.EnableSubtitlesInManifest) { - list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString().ToLower())); + list.Add(new NameValuePair("EnableSubtitlesInManifest", item.EnableSubtitlesInManifest.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } if (item.EnableMpegtsM2TsMode) { - list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString().ToLower())); + list.Add(new NameValuePair("EnableMpegtsM2TsMode", item.EnableMpegtsM2TsMode.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } if (item.EstimateContentLength) { - list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString().ToLower())); + list.Add(new NameValuePair("EstimateContentLength", item.EstimateContentLength.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } if (item.TranscodeSeekInfo != TranscodeSeekInfo.Auto) { - list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLower())); + list.Add(new NameValuePair("TranscodeSeekInfo", item.TranscodeSeekInfo.ToString().ToLowerInvariant())); } if (item.CopyTimestamps) { - list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString().ToLower())); + list.Add(new NameValuePair("CopyTimestamps", item.CopyTimestamps.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } - list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString().ToLower())); + list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } list.Add(new NameValuePair("Tag", item.MediaSource.ETag ?? string.Empty)); @@ -316,7 +316,7 @@ namespace MediaBrowser.Model.Dlna list.Add(new NameValuePair("MinSegments", item.MinSegments.Value.ToString(CultureInfo.InvariantCulture))); } - list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString())); + list.Add(new NameValuePair("BreakOnNonKeyFrames", item.BreakOnNonKeyFrames.ToString(CultureInfo.InvariantCulture))); } foreach (var pair in item.StreamOptions) @@ -332,7 +332,7 @@ namespace MediaBrowser.Model.Dlna if (!item.IsDirectStream) { - list.Add(new NameValuePair("TranscodeReasons", string.Join(",", item.TranscodeReasons.Distinct().Select(i => i.ToString()).ToArray()))); + list.Add(new NameValuePair("TranscodeReasons", string.Join(",", item.TranscodeReasons.Distinct().Select(i => i.ToString())))); } return list; diff --git a/MediaBrowser.Model/Drawing/ImageSize.cs b/MediaBrowser.Model/Drawing/ImageDimensions.cs similarity index 96% rename from MediaBrowser.Model/Drawing/ImageSize.cs rename to MediaBrowser.Model/Drawing/ImageDimensions.cs index 75591d83d..e7805ac49 100644 --- a/MediaBrowser.Model/Drawing/ImageSize.cs +++ b/MediaBrowser.Model/Drawing/ImageDimensions.cs @@ -1,7 +1,7 @@ namespace MediaBrowser.Model.Drawing { /// - /// Struct ImageSize + /// Struct ImageDimensions /// public struct ImageDimensions { diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 3e267a39d..b382d9d4a 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -116,16 +116,8 @@ namespace MediaBrowser.Model.Dto /// The critic rating. public float? CriticRating { get; set; } - /// - /// Gets or sets the game system. - /// - /// The game system. - public string GameSystem { get; set; } - public string[] ProductionLocations { get; set; } - public string[] MultiPartGameFiles { get; set; } - /// /// Gets or sets the path. /// @@ -604,11 +596,6 @@ namespace MediaBrowser.Model.Dto /// The episode count. public int? EpisodeCount { get; set; } /// - /// Gets or sets the game count. - /// - /// The game count. - public int? GameCount { get; set; } - /// /// Gets or sets the song count. /// /// The song count. diff --git a/MediaBrowser.Model/Dto/GameSystemSummary.cs b/MediaBrowser.Model/Dto/GameSystemSummary.cs deleted file mode 100644 index e2400a744..000000000 --- a/MediaBrowser.Model/Dto/GameSystemSummary.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Dto -{ - /// - /// Class GameSystemSummary - /// - public class GameSystemSummary - { - /// - /// Gets or sets the name. - /// - /// The name. - public string Name { get; set; } - - /// - /// Gets or sets the name. - /// - /// The name. - public string DisplayName { get; set; } - - /// - /// Gets or sets the game count. - /// - /// The game count. - public int GameCount { get; set; } - - /// - /// Gets or sets the game extensions. - /// - /// The game extensions. - public string[] GameFileExtensions { get; set; } - - /// - /// Gets or sets the client installed game count. - /// - /// The client installed game count. - public int ClientInstalledGameCount { get; set; } - - /// - /// Initializes a new instance of the class. - /// - public GameSystemSummary() - { - GameFileExtensions = Array.Empty(); - } - } -} diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs index da941d258..ec5adab85 100644 --- a/MediaBrowser.Model/Dto/ItemCounts.cs +++ b/MediaBrowser.Model/Dto/ItemCounts.cs @@ -20,19 +20,9 @@ namespace MediaBrowser.Model.Dto /// /// The episode count. public int EpisodeCount { get; set; } - /// - /// Gets or sets the game count. - /// - /// The game count. - public int GameCount { get; set; } public int ArtistCount { get; set; } public int ProgramCount { get; set; } /// - /// Gets or sets the game system count. - /// - /// The game system count. - public int GameSystemCount { get; set; } - /// /// Gets or sets the trailer count. /// /// The trailer count. diff --git a/MediaBrowser.Model/Dto/UserDto.cs b/MediaBrowser.Model/Dto/UserDto.cs index b00f5919f..13da018a6 100644 --- a/MediaBrowser.Model/Dto/UserDto.cs +++ b/MediaBrowser.Model/Dto/UserDto.cs @@ -1,6 +1,5 @@ using System; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Connect; using MediaBrowser.Model.Users; namespace MediaBrowser.Model.Dto @@ -29,22 +28,6 @@ namespace MediaBrowser.Model.Dto /// The name of the server. public string ServerName { get; set; } - /// - /// Gets or sets the name of the connect user. - /// - /// The name of the connect user. - public string ConnectUserName { get; set; } - /// - /// Gets or sets the connect user identifier. - /// - /// The connect user identifier. - public string ConnectUserId { get; set; } - /// - /// Gets or sets the type of the connect link. - /// - /// The type of the connect link. - public UserLinkType? ConnectLinkType { get; set; } - /// /// Gets or sets the id. /// diff --git a/MediaBrowser.Model/Entities/CollectionType.cs b/MediaBrowser.Model/Entities/CollectionType.cs index bda166118..e26d1b8c3 100644 --- a/MediaBrowser.Model/Entities/CollectionType.cs +++ b/MediaBrowser.Model/Entities/CollectionType.cs @@ -18,7 +18,6 @@ namespace MediaBrowser.Model.Entities public const string Books = "books"; public const string Photos = "photos"; - public const string Games = "games"; public const string LiveTv = "livetv"; public const string Playlists = "playlists"; public const string Folders = "folders"; diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index e0c3bead1..fc346df37 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Model.Entities attributes.Add("Default"); } - return string.Join(" ", attributes.ToArray()); + return string.Join(" ", attributes); } if (Type == MediaStreamType.Video) @@ -120,10 +120,10 @@ namespace MediaBrowser.Model.Entities if (!string.IsNullOrEmpty(Codec)) { - attributes.Add(Codec.ToUpper()); + attributes.Add(Codec.ToUpperInvariant()); } - return string.Join(" ", attributes.ToArray()); + return string.Join(" ", attributes); } if (Type == MediaStreamType.Subtitle) diff --git a/MediaBrowser.Model/Entities/MediaType.cs b/MediaBrowser.Model/Entities/MediaType.cs index af233e61e..c56c8f8f2 100644 --- a/MediaBrowser.Model/Entities/MediaType.cs +++ b/MediaBrowser.Model/Entities/MediaType.cs @@ -14,10 +14,6 @@ namespace MediaBrowser.Model.Entities /// public const string Audio = "Audio"; /// - /// The game - /// - public const string Game = "Game"; - /// /// The photo /// public const string Photo = "Photo"; diff --git a/MediaBrowser.Model/Entities/MetadataProviders.cs b/MediaBrowser.Model/Entities/MetadataProviders.cs index 399961603..e9802cf46 100644 --- a/MediaBrowser.Model/Entities/MetadataProviders.cs +++ b/MediaBrowser.Model/Entities/MetadataProviders.cs @@ -5,7 +5,6 @@ namespace MediaBrowser.Model.Entities /// public enum MetadataProviders { - Gamesdb = 1, /// /// The imdb /// diff --git a/MediaBrowser.Model/Extensions/LinqExtensions.cs b/MediaBrowser.Model/Extensions/LinqExtensions.cs deleted file mode 100644 index f0febf1d0..000000000 --- a/MediaBrowser.Model/Extensions/LinqExtensions.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; - -// TODO: @bond Remove -namespace MediaBrowser.Model.Extensions -{ - // MoreLINQ - Extensions to LINQ to Objects - // Copyright (c) 2008 Jonathan Skeet. All rights reserved. - // - // Licensed under the Apache License, Version 2.0 (the "License"); - // you may not use this file except in compliance with the License. - // You may obtain a copy of the License at - // - // http://www.apache.org/licenses/LICENSE-2.0 - // - // Unless required by applicable law or agreed to in writing, software - // distributed under the License is distributed on an "AS IS" BASIS, - // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - // See the License for the specific language governing permissions and - // limitations under the License. - - public static class LinqExtensions - { - /// - /// Returns all distinct elements of the given source, where "distinctness" - /// is determined via a projection and the default equality comparer for the projected type. - /// - /// - /// This operator uses deferred execution and streams the results, although - /// a set of already-seen keys is retained. If a key is seen multiple times, - /// only the first element with that key is returned. - /// - /// Type of the source sequence - /// Type of the projected element - /// Source sequence - /// Projection for determining "distinctness" - /// A sequence consisting of distinct elements from the source sequence, - /// comparing them by the specified key projection. - - public static IEnumerable DistinctBy(this IEnumerable source, - Func keySelector) - { - return source.DistinctBy(keySelector, null); - } - - /// - /// Returns all distinct elements of the given source, where "distinctness" - /// is determined via a projection and the specified comparer for the projected type. - /// - /// - /// This operator uses deferred execution and streams the results, although - /// a set of already-seen keys is retained. If a key is seen multiple times, - /// only the first element with that key is returned. - /// - /// Type of the source sequence - /// Type of the projected element - /// Source sequence - /// Projection for determining "distinctness" - /// The equality comparer to use to determine whether or not keys are equal. - /// If null, the default equality comparer for TSource is used. - /// A sequence consisting of distinct elements from the source sequence, - /// comparing them by the specified key projection. - - public static IEnumerable DistinctBy(this IEnumerable source, - Func keySelector, IEqualityComparer comparer) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - return DistinctByImpl(source, keySelector, comparer); - } - - private static IEnumerable DistinctByImpl(IEnumerable source, - Func keySelector, IEqualityComparer comparer) - { - var knownKeys = new HashSet(comparer); - foreach (var element in source) - { - if (knownKeys.Add(keySelector(element))) - { - yield return element; - } - } - } - } -} diff --git a/MediaBrowser.Model/Extensions/StringHelper.cs b/MediaBrowser.Model/Extensions/StringHelper.cs index 78e23e767..75ba12a17 100644 --- a/MediaBrowser.Model/Extensions/StringHelper.cs +++ b/MediaBrowser.Model/Extensions/StringHelper.cs @@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Extensions public static string FirstToUpper(this string str) { - return string.IsNullOrEmpty(str) ? "" : str.Substring(0, 1).ToUpper() + str.Substring(1); + return string.IsNullOrEmpty(str) ? string.Empty : str.Substring(0, 1).ToUpperInvariant() + str.Substring(1); } } } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index 05efb6681..a9ce60a2a 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using MediaBrowser.Model.Entities; namespace MediaBrowser.Model.Globalization @@ -17,12 +19,12 @@ namespace MediaBrowser.Model.Globalization /// Gets the countries. /// /// IEnumerable{CountryInfo}. - CountryInfo[] GetCountries(); + Task GetCountries(); /// /// Gets the parental ratings. /// /// IEnumerable{ParentalRating}. - ParentalRating[] GetParentalRatings(); + IEnumerable GetParentalRatings(); /// /// Gets the rating level. /// @@ -51,8 +53,6 @@ namespace MediaBrowser.Model.Globalization /// IEnumerable{LocalizatonOption}. LocalizationOption[] GetLocalizationOptions(); - string RemoveDiacritics(string text); - string NormalizeFormKD(string text); bool HasUnicodeCategory(string value, UnicodeCategory category); diff --git a/MediaBrowser.Model/MediaInfo/AudioCodec.cs b/MediaBrowser.Model/MediaInfo/AudioCodec.cs index 5ed67fd78..5ebdb99cb 100644 --- a/MediaBrowser.Model/MediaInfo/AudioCodec.cs +++ b/MediaBrowser.Model/MediaInfo/AudioCodec.cs @@ -8,9 +8,12 @@ namespace MediaBrowser.Model.MediaInfo public static string GetFriendlyName(string codec) { - if (string.IsNullOrEmpty(codec)) return ""; + if (string.IsNullOrEmpty(codec)) + { + return string.Empty; + } - switch (codec.ToLower()) + switch (codec.ToLowerInvariant()) { case "ac3": return "Dolby Digital"; @@ -19,7 +22,7 @@ namespace MediaBrowser.Model.MediaInfo case "dca": return "DTS"; default: - return codec.ToUpper(); + return codec.ToUpperInvariant(); } } } diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs index fe13413e2..659abe84c 100644 --- a/MediaBrowser.Model/Net/MimeTypes.cs +++ b/MediaBrowser.Model/Net/MimeTypes.cs @@ -158,7 +158,7 @@ namespace MediaBrowser.Model.Net // Catch-all for all video types that don't require specific mime types if (VideoFileExtensionsDictionary.ContainsKey(ext)) { - return "video/" + ext.TrimStart('.').ToLower(); + return "video/" + ext.TrimStart('.').ToLowerInvariant(); } // Type text diff --git a/MediaBrowser.Model/Notifications/NotificationType.cs b/MediaBrowser.Model/Notifications/NotificationType.cs index 9d16e4a16..38b519a14 100644 --- a/MediaBrowser.Model/Notifications/NotificationType.cs +++ b/MediaBrowser.Model/Notifications/NotificationType.cs @@ -5,10 +5,8 @@ namespace MediaBrowser.Model.Notifications ApplicationUpdateAvailable, ApplicationUpdateInstalled, AudioPlayback, - GamePlayback, VideoPlayback, AudioPlaybackStopped, - GamePlaybackStopped, VideoPlaybackStopped, InstallationFailed, PluginError, diff --git a/MediaBrowser.Model/Providers/RemoteSearchResult.cs b/MediaBrowser.Model/Providers/RemoteSearchResult.cs index 88e3bc69c..6e46b1556 100644 --- a/MediaBrowser.Model/Providers/RemoteSearchResult.cs +++ b/MediaBrowser.Model/Providers/RemoteSearchResult.cs @@ -30,8 +30,6 @@ namespace MediaBrowser.Model.Providers public string ImageUrl { get; set; } public string SearchProviderName { get; set; } - - public string GameSystem { get; set; } public string Overview { get; set; } public RemoteSearchResult AlbumArtist { get; set; } diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 1b20f43ac..6a71e3bb3 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -71,8 +71,6 @@ namespace MediaBrowser.Model.Querying public const string VideoBitRate = "VideoBitRate"; public const string AirTime = "AirTime"; public const string Studio = "Studio"; - public const string Players = "Players"; - public const string GameSystem = "GameSystem"; public const string IsFavoriteOrLiked = "IsFavoriteOrLiked"; public const string DateLastContentAdded = "DateLastContentAdded"; public const string SeriesDatePlayed = "SeriesDatePlayed"; diff --git a/MediaBrowser.Model/Threading/ITimer.cs b/MediaBrowser.Model/Threading/ITimer.cs deleted file mode 100644 index 2bec22266..000000000 --- a/MediaBrowser.Model/Threading/ITimer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Threading -{ - public interface ITimer : IDisposable - { - void Change(TimeSpan dueTime, TimeSpan period); - void Change(int dueTimeMs, int periodMs); - } -} diff --git a/MediaBrowser.Model/Threading/ITimerFactory.cs b/MediaBrowser.Model/Threading/ITimerFactory.cs deleted file mode 100644 index 1161958a4..000000000 --- a/MediaBrowser.Model/Threading/ITimerFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Threading -{ - public interface ITimerFactory - { - ITimer Create(Action callback, object state, TimeSpan dueTime, TimeSpan period); - ITimer Create(Action callback, object state, int dueTimeMs, int periodMs); - } -} diff --git a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs b/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs deleted file mode 100644 index 2034848de..000000000 --- a/MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.GameGenres -{ - public class GameGenreMetadataService : MetadataService - { - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - } - - public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/Games/GameMetadataService.cs b/MediaBrowser.Providers/Games/GameMetadataService.cs deleted file mode 100644 index 764394a21..000000000 --- a/MediaBrowser.Providers/Games/GameMetadataService.cs +++ /dev/null @@ -1,36 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Games -{ - public class GameMetadataService : MetadataService - { - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - var sourceItem = source.Item; - var targetItem = target.Item; - - if (replaceData || string.IsNullOrEmpty(targetItem.GameSystem)) - { - targetItem.GameSystem = sourceItem.GameSystem; - } - - if (replaceData || !targetItem.PlayersSupported.HasValue) - { - targetItem.PlayersSupported = sourceItem.PlayersSupported; - } - } - - public GameMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs b/MediaBrowser.Providers/Games/GameSystemMetadataService.cs deleted file mode 100644 index 5bca4731c..000000000 --- a/MediaBrowser.Providers/Games/GameSystemMetadataService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Providers.Manager; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.Providers.Games -{ - public class GameSystemMetadataService : MetadataService - { - protected override void MergeData(MetadataResult source, MetadataResult target, MetadataFields[] lockedFields, bool replaceData, bool mergeMetadataSettings) - { - ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); - - var sourceItem = source.Item; - var targetItem = target.Item; - - if (replaceData || string.IsNullOrEmpty(targetItem.GameSystemName)) - { - targetItem.GameSystemName = sourceItem.GameSystemName; - } - } - - public GameSystemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) - { - } - } -} diff --git a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs index b87f688e1..10ff2515c 100644 --- a/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs +++ b/MediaBrowser.Providers/Manager/GenericPriorityQueue.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +//TODO Fix namespace or replace namespace Priority_Queue { /// diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index dee473374..ab906809f 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -420,7 +420,7 @@ namespace MediaBrowser.Providers.Manager filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex); break; default: - filename = type.ToString().ToLower(); + filename = type.ToString().ToLowerInvariant(); break; } @@ -429,7 +429,7 @@ namespace MediaBrowser.Providers.Manager extension = ".jpg"; } - extension = extension.ToLower(); + extension = extension.ToLowerInvariant(); string path = null; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 493c97b6e..033aea146 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.Manager } else { - var mimeType = "image/" + response.Format.ToString().ToLower(); + var mimeType = "image/" + response.Format.ToString().ToLowerInvariant(); await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 1972ad290..77028e526 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -566,8 +566,7 @@ namespace MediaBrowser.Providers.Manager var providersWithChanges = providers .Where(i => { - var hasFileChangeMonitor = i as IHasItemChangeMonitor; - if (hasFileChangeMonitor != null) + if (i is IHasItemChangeMonitor hasFileChangeMonitor) { return HasChanged(item, hasFileChangeMonitor, options.DirectoryService); } diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index eda5163f0..f26087fda 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -471,8 +471,6 @@ namespace MediaBrowser.Providers.Manager { return new MetadataPluginSummary[] { - GetPluginSummary(), - GetPluginSummary(), GetPluginSummary(), GetPluginSummary(), GetPluginSummary(), diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index afc760dd4..4e11fcbb2 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -74,17 +74,12 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (item.SupportsLocalMetadata) + if (item.SupportsLocalMetadata && video != null && !video.IsPlaceHolder + && !video.SubtitleFiles.SequenceEqual( + _subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) { - if (video != null && !video.IsPlaceHolder) - { - if (!video.SubtitleFiles - .SequenceEqual(_subtitleResolver.GetExternalSubtitleFiles(video, directoryService, false), StringComparer.Ordinal)) - { - _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); - return true; - } - } + _logger.LogDebug("Refreshing {0} due to external subtitles change.", item.Path); + return true; } return false; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index b6f862f9a..cd026b39b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.MediaInfo continue; } - var codec = Path.GetExtension(fullName).ToLower().TrimStart('.'); + var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.'); if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs index b7598ef22..74f41f9df 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs @@ -36,12 +36,6 @@ namespace MediaBrowser.Providers.MediaInfo _json = json; } - public string Name => "Download missing subtitles"; - - public string Description => "Searches the internet for missing subtitles based on metadata configuration."; - - public string Category => "Library"; - private SubtitleOptions GetOptions() { return _config.GetConfiguration("subtitles"); @@ -204,6 +198,18 @@ namespace MediaBrowser.Providers.MediaInfo }; } + public string Name => "Download missing subtitles"; + + public string Description => "Searches the internet for missing subtitles based on metadata configuration."; + + public string Category => "Library"; + public string Key => "DownloadSubtitles"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; } } diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs index 4a94bcb1a..70d187bf5 100644 --- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs +++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs @@ -20,6 +20,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; using MediaBrowser.Providers.TV; +using MediaBrowser.Providers.TV.FanArt; namespace MediaBrowser.Providers.Movies { diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index c5af5ef36..3ff63a4bf 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -285,7 +285,7 @@ namespace MediaBrowser.Providers.Movies if (parts.Length == 2) { - language = parts[0] + "-" + parts[1].ToUpper(); + language = parts[0] + "-" + parts[1].ToUpperInvariant(); } } diff --git a/MediaBrowser.Providers/Movies/MovieDbSearch.cs b/MediaBrowser.Providers/Movies/MovieDbSearch.cs index 47d3d21bd..e466d40a0 100644 --- a/MediaBrowser.Providers/Movies/MovieDbSearch.cs +++ b/MediaBrowser.Providers/Movies/MovieDbSearch.cs @@ -72,7 +72,7 @@ namespace MediaBrowser.Providers.Movies } _logger.LogInformation("MovieDbProvider: Finding id for item: " + name); - var language = idInfo.MetadataLanguage.ToLower(); + var language = idInfo.MetadataLanguage.ToLowerInvariant(); //nope - search for it //var searchType = item is BoxSet ? "collection" : "movie"; diff --git a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs index 2efeb6985..75b4213c5 100644 --- a/MediaBrowser.Providers/Music/FanArtArtistProvider.cs +++ b/MediaBrowser.Providers/Music/FanArtArtistProvider.cs @@ -20,6 +20,7 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.TV; +using MediaBrowser.Providers.TV.FanArt; namespace MediaBrowser.Providers.Music { diff --git a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs index cf2acdf49..181e88820 100644 --- a/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/People/TvdbPersonImageProvider.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Xml; using MediaBrowser.Providers.TV; +using MediaBrowser.Providers.TV.TheTVDB; namespace MediaBrowser.Providers.People { diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index e247c4f86..7fc6909f5 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -159,14 +159,14 @@ namespace MediaBrowser.Providers.Subtitles memoryStream.Position = 0; var savePaths = new List(); - var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower(); + var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); if (response.IsForced) { saveFileName += ".forced"; } - saveFileName += "." + response.Format.ToLower(); + saveFileName += "." + response.Format.ToLowerInvariant(); if (saveInMediaFolder) { @@ -296,7 +296,7 @@ namespace MediaBrowser.Providers.Subtitles private string GetProviderId(string name) { - return name.ToLower().GetMD5().ToString("N"); + return name.ToLowerInvariant().GetMD5().ToString("N"); } private ISubtitleProvider GetProvider(string id) diff --git a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs index 493729446..58356910f 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanArtSeasonProvider.cs @@ -19,7 +19,7 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.FanArt { public class FanArtSeasonProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs index 172a7d2b8..49cd9596e 100644 --- a/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/FanArt/FanartSeriesProvider.cs @@ -22,7 +22,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.FanArt { public class FanartSeriesProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 4ac012399..25ad36620 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Xml; +using MediaBrowser.Providers.TV.TheTVDB; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.TV diff --git a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs index 6f7d9f791..d0749405b 100644 --- a/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/Omdb/OmdbEpisodeProvider.cs @@ -14,7 +14,7 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Omdb; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.Omdb { class OmdbEpisodeProvider : IRemoteMetadataProvider, diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs index 2482aa8d4..e4248afe1 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeImageProvider.cs @@ -16,7 +16,7 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Movies; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { public class MovieDbEpisodeImageProvider : MovieDbProviderBase, diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs index 347c91742..44590515e 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbEpisodeProvider.cs @@ -18,7 +18,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { class MovieDbEpisodeProvider : MovieDbProviderBase, diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs index 9f1102946..6e438ebd8 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbProviderBase.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Movies; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { public abstract class MovieDbProviderBase { diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs index 790b38074..6be1b101d 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeasonProvider.cs @@ -18,7 +18,7 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Movies; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { public class MovieDbSeasonProvider : IRemoteMetadataProvider { diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs index fdc8cd7f1..26686356f 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesImageProvider.cs @@ -14,7 +14,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Movies; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { public class MovieDbSeriesImageProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs index 76031a7cd..b51fb6af8 100644 --- a/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheMovieDb/MovieDbSeriesProvider.cs @@ -20,7 +20,7 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Movies; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheMovieDb { public class MovieDbSeriesProvider : IRemoteMetadataProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs index 39d2fa77a..102a3d4ec 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeImageProvider.cs @@ -14,7 +14,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { public class TvdbEpisodeImageProvider : IRemoteImageProvider { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs index 25fc214b5..be137e879 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbEpisodeProvider.cs @@ -18,7 +18,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { /// diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs index 6f7cb72d3..d45696057 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -19,7 +19,7 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { /// /// Class TvdbPrescanTask diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs index af36d1ebf..01ede44bb 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeasonImageProvider.cs @@ -19,7 +19,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Xml; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { public class TvdbSeasonImageProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs index 82fa14f49..2b4337ed1 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesImageProvider.cs @@ -19,7 +19,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Xml; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { public class TvdbSeriesImageProvider : IRemoteImageProvider, IHasOrder { diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 496e0bb72..52e60a8ed 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -23,7 +23,7 @@ using MediaBrowser.Model.Providers; using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.TV +namespace MediaBrowser.Providers.TV.TheTVDB { public class TvdbSeriesProvider : IRemoteMetadataProvider, IHasOrder { @@ -66,7 +66,7 @@ namespace MediaBrowser.Providers.TV } // pt-br is just pt to tvdb - return language.Split('-')[0].ToLower(); + return language.Split('-')[0].ToLowerInvariant(); } public async Task> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) @@ -776,7 +776,7 @@ namespace MediaBrowser.Providers.TV /// System.String. private string GetComparableName(string name) { - name = name.ToLower(); + name = name.ToLowerInvariant(); name = _localizationManager.NormalizeFormKD(name); var sb = new StringBuilder(); foreach (var c in name) @@ -1620,7 +1620,7 @@ namespace MediaBrowser.Providers.TV { var seriesDataPath = GetSeriesDataPath(_config.ApplicationPaths, seriesProviderIds); - var seriesXmlFilename = language.ToLower() + ".xml"; + var seriesXmlFilename = language.ToLowerInvariant() + ".xml"; return Path.Combine(seriesDataPath, seriesXmlFilename); } diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 001ce1465..5c246e300 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.TV.TheTVDB; namespace MediaBrowser.Providers.TV { diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 2f4e21443..531978e1d 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -425,11 +425,9 @@ namespace MediaBrowser.WebDashboard.Api private async Task DumpFile(PackageCreator packageCreator, string resourceVirtualPath, string destinationFilePath, string mode, string appVersion) { using (var stream = await packageCreator.GetResource(resourceVirtualPath, mode, null, appVersion).ConfigureAwait(false)) + using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) { - using (var fs = _fileSystem.GetFileStream(destinationFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) - { - stream.CopyTo(fs); - } + await stream.CopyToAsync(fs); } } diff --git a/MediaBrowser.WebDashboard/ServerEntryPoint.cs b/MediaBrowser.WebDashboard/ServerEntryPoint.cs index 221fa62c7..18ed54a78 100644 --- a/MediaBrowser.WebDashboard/ServerEntryPoint.cs +++ b/MediaBrowser.WebDashboard/ServerEntryPoint.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Controller.Plugins; @@ -23,9 +24,11 @@ namespace MediaBrowser.WebDashboard Instance = this; } - public void Run() + public Task RunAsync() { PluginConfigurationPages = _appHost.GetExports().ToList(); + + return Task.CompletedTask; } public void Dispose() diff --git a/MediaBrowser.XbmcMetadata/EntryPoint.cs b/MediaBrowser.XbmcMetadata/EntryPoint.cs index 37a1d4c35..992991a7e 100644 --- a/MediaBrowser.XbmcMetadata/EntryPoint.cs +++ b/MediaBrowser.XbmcMetadata/EntryPoint.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -28,9 +29,11 @@ namespace MediaBrowser.XbmcMetadata _config = config; } - public void Run() + public Task RunAsync() { _userDataManager.UserDataSaved += _userDataManager_UserDataSaved; + + return Task.CompletedTask; } void _userDataManager_UserDataSaved(object sender, UserDataSaveEventArgs e) diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 96b906b3c..4925c7cd1 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -82,7 +82,6 @@ namespace MediaBrowser.XbmcMetadata.Savers "lockedfields", "zap2itid", "tvrageid", - "gamesdbid", "musicbrainzartistid", "musicbrainzalbumartistid", @@ -290,7 +289,7 @@ namespace MediaBrowser.XbmcMetadata.Savers foreach (var stream in mediaStreams) { - writer.WriteStartElement(stream.Type.ToString().ToLower()); + writer.WriteStartElement(stream.Type.ToString().ToLowerInvariant()); if (!string.IsNullOrEmpty(stream.Codec)) { @@ -471,7 +470,7 @@ namespace MediaBrowser.XbmcMetadata.Savers writer.WriteElementString("customrating", item.CustomRating); } - writer.WriteElementString("lockdata", item.IsLocked.ToString().ToLower()); + writer.WriteElementString("lockdata", item.IsLocked.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); if (item.LockedFields.Length > 0) { @@ -737,13 +736,6 @@ namespace MediaBrowser.XbmcMetadata.Savers writtenProviderIds.Add(MetadataProviders.MusicBrainzReleaseGroup.ToString()); } - externalId = item.GetProviderId(MetadataProviders.Gamesdb); - if (!string.IsNullOrEmpty(externalId)) - { - writer.WriteElementString("gamesdbid", externalId); - writtenProviderIds.Add(MetadataProviders.Gamesdb.ToString()); - } - externalId = item.GetProviderId(MetadataProviders.TvRage); if (!string.IsNullOrEmpty(externalId)) { @@ -871,21 +863,21 @@ namespace MediaBrowser.XbmcMetadata.Savers var userdata = userDataRepo.GetUserData(user, item); - writer.WriteElementString("isuserfavorite", userdata.IsFavorite.ToString().ToLower()); + writer.WriteElementString("isuserfavorite", userdata.IsFavorite.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); if (userdata.Rating.HasValue) { - writer.WriteElementString("userrating", userdata.Rating.Value.ToString(CultureInfo.InvariantCulture).ToLower()); + writer.WriteElementString("userrating", userdata.Rating.Value.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); } if (!item.IsFolder) { writer.WriteElementString("playcount", userdata.PlayCount.ToString(UsCulture)); - writer.WriteElementString("watched", userdata.Played.ToString().ToLower()); + writer.WriteElementString("watched", userdata.Played.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); if (userdata.LastPlayedDate.HasValue) { - writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLower()); + writer.WriteElementString("lastplayed", userdata.LastPlayedDate.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss").ToLowerInvariant()); } writer.WriteStartElement("resume"); @@ -901,12 +893,13 @@ namespace MediaBrowser.XbmcMetadata.Savers private void AddActors(List people, XmlWriter writer, ILibraryManager libraryManager, IFileSystem fileSystem, IServerConfigurationManager config, bool saveImagePath) { - var actors = people - .Where(i => !IsPersonType(i, PersonType.Director) && !IsPersonType(i, PersonType.Writer)) - .ToList(); - - foreach (var person in actors) + foreach (var person in people) { + if (IsPersonType(person, PersonType.Director) || IsPersonType(person, PersonType.Writer)) + { + continue; + } + writer.WriteStartElement("actor"); if (!string.IsNullOrWhiteSpace(person.Name)) @@ -1021,7 +1014,7 @@ namespace MediaBrowser.XbmcMetadata.Savers private string GetTagForProviderKey(string providerKey) { - return providerKey.ToLower() + "id"; + return providerKey.ToLowerInvariant() + "id"; } } } diff --git a/README.md b/README.md index dfa8457de..d869c8978 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,17 @@

Logo banner -

-GPL 2.0 License -Build Status -Docker Pull Count -Documentation -Chat on Matrix -Join our Subreddit +

+GPL 2.0 License +Current Release +Translations +Build Status +Docker Pull Count +
+Donate +Discuss on our Forum +Chat on Matrix +Join our Subreddit

--- @@ -24,13 +28,13 @@ For more information about the project, please see our [about page](https://jell

Want to get started? -Choose from Prebuilt Packages or Build from Source, then see our Quickstart guide. +Choose from Prebuilt Packages or Build from Source, then see our first-time setup guide.

Want to contribute? -Check out our documentation for guidelines. +Check out our documentation for guidelines.

New idea or improvement? Something not working right? -Open an Issue. +Open an Issue.

diff --git a/RSSDP/SsdpDeviceLocator.cs b/RSSDP/SsdpDeviceLocator.cs index 1348cce8d..128bdfcbb 100644 --- a/RSSDP/SsdpDeviceLocator.cs +++ b/RSSDP/SsdpDeviceLocator.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Threading; namespace Rssdp.Infrastructure { @@ -23,8 +22,7 @@ namespace Rssdp.Infrastructure private List _Devices; private ISsdpCommunicationsServer _CommunicationsServer; - private ITimer _BroadcastTimer; - private ITimerFactory _timerFactory; + private Timer _BroadcastTimer; private object _timerLock = new object(); private readonly TimeSpan DefaultSearchWaitTime = TimeSpan.FromSeconds(4); @@ -37,12 +35,11 @@ namespace Rssdp.Infrastructure /// /// Default constructor. /// - public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory) + public SsdpDeviceLocator(ISsdpCommunicationsServer communicationsServer) { if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer)); _CommunicationsServer = communicationsServer; - _timerFactory = timerFactory; _CommunicationsServer.ResponseReceived += CommsServer_ResponseReceived; _Devices = new List(); @@ -94,7 +91,7 @@ namespace Rssdp.Infrastructure { if (_BroadcastTimer == null) { - _BroadcastTimer = _timerFactory.Create(OnBroadcastTimerCallback, null, dueTime, period); + _BroadcastTimer = new Timer(OnBroadcastTimerCallback, null, dueTime, period); } else { diff --git a/RSSDP/SsdpDevicePublisher.cs b/RSSDP/SsdpDevicePublisher.cs index 8a73e6a2d..ce64ba117 100644 --- a/RSSDP/SsdpDevicePublisher.cs +++ b/RSSDP/SsdpDevicePublisher.cs @@ -7,7 +7,6 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; -using MediaBrowser.Model.Threading; using Rssdp; namespace Rssdp.Infrastructure @@ -27,8 +26,7 @@ namespace Rssdp.Infrastructure private IList _Devices; private IReadOnlyList _ReadOnlyDevices; - private ITimer _RebroadcastAliveNotificationsTimer; - private ITimerFactory _timerFactory; + private Timer _RebroadcastAliveNotificationsTimer; private IDictionary _RecentSearchRequests; @@ -39,7 +37,7 @@ namespace Rssdp.Infrastructure /// /// Default constructor. /// - public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, ITimerFactory timerFactory, string osName, string osVersion) + public SsdpDevicePublisher(ISsdpCommunicationsServer communicationsServer, string osName, string osVersion) { if (communicationsServer == null) throw new ArgumentNullException(nameof(communicationsServer)); if (osName == null) throw new ArgumentNullException(nameof(osName)); @@ -48,7 +46,6 @@ namespace Rssdp.Infrastructure if (osVersion.Length == 0) throw new ArgumentException("osVersion cannot be an empty string.", nameof(osName)); _SupportPnpRootDevice = true; - _timerFactory = timerFactory; _Devices = new List(); _ReadOnlyDevices = new ReadOnlyCollection(_Devices); _RecentSearchRequests = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -64,7 +61,7 @@ namespace Rssdp.Infrastructure public void StartBroadcastingAliveMessages(TimeSpan interval) { - _RebroadcastAliveNotificationsTimer = _timerFactory.Create(SendAllAliveNotifications, null, TimeSpan.FromSeconds(5), interval); + _RebroadcastAliveNotificationsTimer = new Timer(SendAllAliveNotifications, null, TimeSpan.FromSeconds(5), interval); } /// diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs index b051b6718..a02b48061 100644 --- a/SocketHttpListener/Ext.cs +++ b/SocketHttpListener/Ext.cs @@ -486,7 +486,7 @@ namespace SocketHttpListener if (method == CompressionMethod.None) return string.Empty; - var m = string.Format("permessage-{0}", method.ToString().ToLower()); + var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant()); if (parameters == null || parameters.Length == 0) return m; diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs index faeca78b2..667d58ea7 100644 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ b/SocketHttpListener/Net/HttpListenerRequest.cs @@ -155,7 +155,7 @@ namespace SocketHttpListener.Net } else { - header = header.ToLower(CultureInfo.InvariantCulture); + header = header.ToLowerInvariant(); _keepAlive = header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 || header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0; diff --git a/build b/build index 7d49db72a..3b4167dae 100755 --- a/build +++ b/build @@ -23,8 +23,9 @@ usage() { echo -e "Usage:" echo -e " $ build --list-platforms" echo -e " $ build --list-actions " - echo -e " $ build [-b/--web-branch ] " + echo -e " $ build [-k/--keep-artifacts] [-b/--web-branch ] " echo -e "" + echo -e "The 'keep-artifacts' option preserves build artifacts, e.g. Docker images for system package builds." echo -e "The web_branch defaults to the same branch name as the current main branch." echo -e "To build all platforms, use 'all'." echo -e "To perform all build actions, use 'all'." @@ -67,6 +68,14 @@ if [[ $1 == '--list-actions' ]]; then exit 0 fi +# Parse keep-artifacts option +if [[ $1 == '-k' || $1 == '--keep-artifacts' ]]; then + keep_artifacts="y" + shift 1 +else + keep_artifacts="n" +fi + # Parse branch option if [[ $1 == '-b' || $1 == '--web-branch' ]]; then web_branch="$2" @@ -193,6 +202,13 @@ for target_platform in ${platform[@]}; do echo -e "> Processing platform ${target_platform}" date_start=$( date +%s ) pushd ${target_platform} + cleanup() { + echo -e ">> Processing action clean" + if [[ -f clean.sh && -x clean.sh ]]; then + ./clean.sh ${keep_artifacts} + fi + } + trap cleanup EXIT INT for target_action in ${action[@]}; do echo -e ">> Processing action ${target_action}" if [[ -f ${target_action}.sh && -x ${target_action}.sh ]]; then @@ -204,12 +220,8 @@ for target_platform in ${platform[@]}; do target_dir="../../../jellyfin-build/${target_platform}" mkdir -p ${target_dir} mv pkg-dist/* ${target_dir}/ - - echo -e ">> Processing action clean" - if [[ -f clean.sh && -x clean.sh ]]; then - ./clean.sh - fi fi + cleanup date_end=$( date +%s ) echo -e "> Completed platform ${target_platform} in $( expr ${date_end} - ${date_start} ) seconds." popd diff --git a/bump_version b/bump_version index c3f1a78d5..a63fbf735 100755 --- a/bump_version +++ b/bump_version @@ -68,8 +68,7 @@ new_version="$1" # Parse the version from the AssemblyVersion old_version="$( grep "AssemblyVersion" ${shared_version_file} \ - | sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' \ - | sed -E 's/.0$//' + | sed -E 's/\[assembly: ?AssemblyVersion\("([0-9\.]+)"\)\]/\1/' )" # Set the shared version to the specified new_version diff --git a/deployment/README.md b/deployment/README.md index 05b4ed51e..a00cd3e6c 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -55,6 +55,8 @@ These builds are not necessarily run from the `build` script, but are present fo * The `clean` action should always `exit 0` even if no work is done or it fails. +* The `clean` action can be passed a variable as argument 1, named `keep_artifacts`, containing either the value `y` or `n`. It is indended to handle situations when the user runs `build --keep-artifacts` and should be handled intelligently. Usually, this is used to preserve Docker images while still removing temporary directories. + ### Output Files * Upon completion of the defined actions, at least one output file must be created in the `/pkg-dist` directory. diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile new file mode 100644 index 000000000..38853f173 --- /dev/null +++ b/deployment/centos-package-x64/Dockerfile @@ -0,0 +1,27 @@ +FROM centos:7 +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist + +# Prepare CentOS build environment +RUN yum update -y \ + && yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ + && rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \ + && rpmdev-setuptree \ + && yum install -y dotnet-sdk-${SDK_VERSION} \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/centos-package-x64/clean.sh b/deployment/centos-package-x64/clean.sh new file mode 100755 index 000000000..7278372e1 --- /dev/null +++ b/deployment/centos-package-x64/clean.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" +VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +package_source_dir="${WORKDIR}/pkg-src" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-centos-build" + +rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ + || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/centos-package-x64/dependencies.txt b/deployment/centos-package-x64/dependencies.txt new file mode 100644 index 000000000..bdb967096 --- /dev/null +++ b/deployment/centos-package-x64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh new file mode 100755 index 000000000..3acf1ec0d --- /dev/null +++ b/deployment/centos-package-x64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the RPM inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +ls -al SOURCES/pkg-src/ + +# Build RPM +spectool -g -R SPECS/jellyfin.spec +rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/rpm +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ diff --git a/deployment/centos-package-x64/package.sh b/deployment/centos-package-x64/package.sh new file mode 100755 index 000000000..27d686e46 --- /dev/null +++ b/deployment/centos-package-x64/package.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +WORKDIR="$( pwd )" +VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +pkg_src_dir="${WORKDIR}/pkg-src" +current_user="$( whoami )" +image_name="jellyfin-centos-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Create RPM source archive +GNU_TAR=1 +mkdir -p "${package_temporary_dir}" +echo "Bundling all sources for RPM build." +tar \ +--transform "s,^\.,jellyfin-${VERSION}," \ +--exclude='.git*' \ +--exclude='**/.git' \ +--exclude='**/.hg' \ +--exclude='**/.vs' \ +--exclude='**/.vscode' \ +--exclude='deployment' \ +--exclude='**/bin' \ +--exclude='**/obj' \ +--exclude='**/.nuget' \ +--exclude='*.deb' \ +--exclude='*.rpm' \ +-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \ +-C "../.." ./ || GNU_TAR=0 + +if [ $GNU_TAR -eq 0 ]; then + echo "The installed tar binary did not support --transform. Using workaround." + mkdir -p "${package_temporary_dir}/jellyfin" + # Not GNU tar + tar \ + --exclude='.git*' \ + --exclude='**/.git' \ + --exclude='**/.hg' \ + --exclude='**/.vs' \ + --exclude='**/.vscode' \ + --exclude='deployment' \ + --exclude='**/bin' \ + --exclude='**/obj' \ + --exclude='**/.nuget' \ + --exclude='*.deb' \ + --exclude='*.rpm' \ + -zcf \ + "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ + -C "../.." ./ + echo "Extracting filtered package." + tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" + echo "Removing filtered package." + rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" + echo "Repackaging package into final tarball." + tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" +fi + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the RPMs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the RPMs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" +# Move the RPMs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/centos-package-x64/pkg-src b/deployment/centos-package-x64/pkg-src new file mode 120000 index 000000000..dfd6497cf --- /dev/null +++ b/deployment/centos-package-x64/pkg-src @@ -0,0 +1 @@ +../fedora-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile index 2afe6c1d3..9819cc20d 100644 --- a/deployment/debian-package-x64/Dockerfile +++ b/deployment/debian-package-x64/Dockerfile @@ -1,23 +1,21 @@ -FROM debian:9 -ARG SOURCEDIR=/repo +FROM microsoft/dotnet:2.2-sdk-stretch +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/debian-package-x64 +ARG ARTIFACT_DIR=/dist +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist ENV DEB_BUILD_OPTIONS=noddebs -# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current +# Prepare Debian build environment RUN apt-get update \ - && apt-get install -y apt-transport-https debhelper gnupg wget devscripts \ - && wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg \ - && mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/ \ - && wget -q https://packages.microsoft.com/config/debian/9/prod.list \ - && mv prod.list /etc/apt/sources.list.d/microsoft-prod.list \ - && chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg \ - && chown root:root /etc/apt/sources.list.d/microsoft-prod.list \ - && apt-get update + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian -WORKDIR ${SOURCEDIR} -COPY . . -COPY ./deployment/debian-package-x64/pkg-src ./debian +VOLUME ${ARTIFACT_DIR}/ -RUN yes | mk-build-deps -i debian/control \ - && dpkg-buildpackage -us -uc +COPY . ${SOURCE_DIR}/ -WORKDIR / +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/debian-package-x64/clean.sh b/deployment/debian-package-x64/clean.sh index 3df2d7796..b2960fcb3 100755 --- a/deployment/debian-package-x64/clean.sh +++ b/deployment/debian-package-x64/clean.sh @@ -2,6 +2,28 @@ source ../common.build.sh -VERSION=`get_version ../..` +keep_artifacts="${1}" -clean_jellyfin ../.. Release `pwd`/dist/jellyfin_${VERSION} +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh new file mode 100755 index 000000000..0590be097 --- /dev/null +++ b/deployment/debian-package-x64/docker-build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + +# Build DEB +dpkg-buildpackage -us -uc + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/debian-package-x64/package.sh b/deployment/debian-package-x64/package.sh index dec953961..d7c3f5809 100755 --- a/deployment/debian-package-x64/package.sh +++ b/deployment/debian-package-x64/package.sh @@ -2,30 +2,30 @@ source ../common.build.sh -VERSION=`get_version ../..` +WORKDIR="$( pwd )" -# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version. +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-debian-build" -# Build a Jellyfin .deb file with Docker on Linux -# Places the output .deb file in the parent directory +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi -package_temporary_dir="`pwd`/pkg-dist-tmp" -output_dir="`pwd`/pkg-dist" -current_user="`whoami`" -image_name="jellyfin-debuild" - -cleanup() { - set +o errexit - docker image rm $image_name --force - rm -rf "$package_temporary_dir" -} -trap cleanup EXIT INT - -docker build ../.. -t "$image_name" -f ./Dockerfile --build-arg SOURCEDIR="/jellyfin-${VERSION}" -mkdir -p "$package_temporary_dir" -mkdir -p "$output_dir" -docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find / -maxdepth 1 -type f -name "jellyfin*" -exec mv {} /temp \;' -chown -R "$current_user" "$package_temporary_dir" \ -|| sudo chown -R "$current_user" "$package_temporary_dir" - -mv "$package_temporary_dir"/* "$output_dir" +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/debian-package-x64/pkg-src/bin/restart.sh b/deployment/debian-package-x64/pkg-src/bin/restart.sh index a6f4632ba..738f86727 100644 --- a/deployment/debian-package-x64/pkg-src/bin/restart.sh +++ b/deployment/debian-package-x64/pkg-src/bin/restart.sh @@ -2,10 +2,12 @@ NAME=jellyfin -restart_cmds=("s6-svc -t /var/run/s6/services/${NAME}" \ - "systemctl restart ${NAME}" \ - "service ${NAME} restart" \ - "/etc/init.d/${NAME} restart") +restart_cmds=( + "systemctl restart ${NAME}" + "service ${NAME} restart" + "/etc/init.d/${NAME} restart" + "s6-svc -t /var/run/s6/services/${NAME}" +) for restart_cmd in "${restart_cmds[@]}"; do cmd=$(echo "$restart_cmd" | awk '{print $1}') diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index 861865aae..b052b2ec6 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -1,4 +1,5 @@ # Jellyfin default configuration options +# This is a POSIX shell fragment # Use this file to override the default configurations; add additional # options with JELLYFIN_ADD_OPTS. @@ -7,10 +8,6 @@ # /etc/systemd/system/jellyfin.service.d/jellyfin.service.conf # to override the user or this config file's location. -# -# This is a POSIX shell fragment -# - # # General options # @@ -19,10 +16,20 @@ JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin" JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin" JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" +JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin" + # Restart script for in-app server control -JELLYFIN_RESTART_SCRIPT="/usr/lib/jellyfin/restart.sh" -# Additional options for the binary -JELLYFIN_ADD_OPTS="" +JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" + +# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values +#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" +#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" + +# [OPTIONAL] run Jellyfin as a headless service +#JELLYFIN_SERVICE_OPT="--service" + +# [OPTIONAL] run Jellyfin without the web app +#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" # # SysV init/Upstart options @@ -31,4 +38,4 @@ JELLYFIN_ADD_OPTS="" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="-programdata $JELLYFIN_DATA_DIRECTORY -configdir $JELLYFIN_CONFIG_DIRECTORY -logdir $JELLYFIN_LOG_DIRECTORY -restartpath $JELLYFIN_RESTART_SCRIPT $JELLYFIN_ADD_OPTS" +JELLYFIN_ARGS="--datadir=$JELLYFIN_DATA_DIRECTORY --configdir=$JELLYFIN_CONFIG_DIRECTORY --logdir=$JELLYFIN_LOG_DIRECTORY --cachedir=$JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/bin/jellyfin-sudoers b/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers similarity index 68% rename from deployment/debian-package-x64/pkg-src/bin/jellyfin-sudoers rename to deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers index 4eb91366b..b481ba4ad 100644 --- a/deployment/debian-package-x64/pkg-src/bin/jellyfin-sudoers +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin-sudoers @@ -10,15 +10,15 @@ Cmnd_Alias STARTSERVER_INITD = /etc/init.d/jellyfin start Cmnd_Alias STOPSERVER_INITD = /etc/init.d/jellyfin stop -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSV +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_INITD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_INITD Defaults!RESTARTSERVER_SYSV !requiretty Defaults!STARTSERVER_SYSV !requiretty @@ -31,7 +31,7 @@ Defaults!STARTSERVER_INITD !requiretty Defaults!STOPSERVER_INITD !requiretty #Allow the server to mount iso images -%jellyfin ALL=(ALL) NOPASSWD: /bin/mount -%jellyfin ALL=(ALL) NOPASSWD: /bin/umount +jellyfin ALL=(ALL) NOPASSWD: /bin/mount +jellyfin ALL=(ALL) NOPASSWD: /bin/umount -Defaults:%jellyfin !requiretty +Defaults:jellyfin !requiretty diff --git a/deployment/debian-package-x64/pkg-src/install b/deployment/debian-package-x64/pkg-src/install index adaff7b26..994322d14 100644 --- a/deployment/debian-package-x64/pkg-src/install +++ b/deployment/debian-package-x64/pkg-src/install @@ -2,5 +2,5 @@ usr/lib/jellyfin usr/lib/ debian/conf/jellyfin etc/default/ debian/conf/logging.json etc/jellyfin/ debian/conf/jellyfin.service.conf etc/systemd/system/jellyfin.service.d/ -debian/bin/jellyfin-sudoers etc/sudoers.d/ +debian/conf/jellyfin-sudoers etc/sudoers.d/ debian/bin/restart.sh usr/lib/jellyfin/ diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/deployment/debian-package-x64/pkg-src/jellyfin.service index c17422029..9c6c6667f 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/deployment/debian-package-x64/pkg-src/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin -programdata ${JELLYFIN_DATA_DIRECTORY} -configdir ${JELLYFIN_CONFIG_DIRECTORY} -logdir ${JELLYFIN_LOG_DIRECTORY} -restartpath ${JELLYFIN_RESTART_SCRIPT} ${JELLYFIN_ADD_OPTS} +ExecStart = /usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 diff --git a/deployment/debian-package-x64/pkg-src/postinst b/deployment/debian-package-x64/pkg-src/postinst index 3690d20ba..860222e05 100644 --- a/deployment/debian-package-x64/pkg-src/postinst +++ b/deployment/debian-package-x64/pkg-src/postinst @@ -13,6 +13,7 @@ fi PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} +CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} case "$1" in configure) @@ -37,10 +38,14 @@ case "$1" in if [[ ! -d $LOGDATA ]]; then mkdir $LOGDATA fi + # ensure $CACHEDATA exists + if [[ ! -d $CACHEDATA ]]; then + mkdir $CACHEDATA + fi # Ensure permissions are correct on all config directories - chown -R jellyfin:jellyfin $PROGRAMDATA - chown -R jellyfin:jellyfin $CONFIGDATA - chown -R jellyfin:jellyfin $LOGDATA + chown -R jellyfin $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA + chgrp adm $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA + chmod 0750 $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chmod +x /usr/lib/jellyfin/restart.sh > /dev/null 2>&1 || true diff --git a/deployment/debian-package-x64/pkg-src/postrm b/deployment/debian-package-x64/pkg-src/postrm index 690f5d587..1d00a984e 100644 --- a/deployment/debian-package-x64/pkg-src/postrm +++ b/deployment/debian-package-x64/pkg-src/postrm @@ -12,7 +12,8 @@ fi # Data directories for program data (cache, db), configs, and logs PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} -LOGDATA=${JELLYFIN_DATA_DIRECTORY-/var/log/$NAME} +LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} +CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} # In case this system is running systemd, we make systemd reload the unit files # to pick up changes. @@ -44,6 +45,10 @@ case "$1" in if [[ -d $LOGDATA ]]; then rm -rf $LOGDATA fi + # Remove cache dir + if [[ -d $CACHEDATA ]]; then + rm -rf $CACHEDATA + fi # Remove program data dir if [[ -d $PROGRAMDATA ]]; then rm -rf $PROGRAMDATA @@ -55,6 +60,7 @@ case "$1" in # Remove anything at the default locations; catches situations where the user moved the defaults [[ -e /etc/jellyfin ]] && rm -rf /etc/jellyfin [[ -e /var/log/jellyfin ]] && rm -rf /var/log/jellyfin + [[ -e /var/cache/jellyfin ]] && rm -rf /var/cache/jellyfin [[ -e /var/lib/jellyfin ]] && rm -rf /var/lib/jellyfin ;; remove) diff --git a/deployment/debian-package-x64/pkg-src/preinst b/deployment/debian-package-x64/pkg-src/preinst index 0063e0e63..2713fb9b8 100644 --- a/deployment/debian-package-x64/pkg-src/preinst +++ b/deployment/debian-package-x64/pkg-src/preinst @@ -12,7 +12,8 @@ fi # Data directories for program data (cache, db), configs, and logs PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} -LOGDATA=${JELLYFIN_DATA_DIRECTORY-/var/log/$NAME} +LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} +CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} # In case this system is running systemd, we make systemd reload the unit files # to pick up changes. @@ -53,13 +54,16 @@ case "$1" in # Clean up old Emby cruft that can break the user's system [[ -f /etc/sudoers.d/emby ]] && rm -f /etc/sudoers.d/emby - # If we have existing config or log dirs in /var/lib/jellyfin, move them into the right place + # If we have existing config, log, or cache dirs in /var/lib/jellyfin, move them into the right place if [[ -d $PROGRAMDATA/config ]]; then mv $PROGRAMDATA/config $CONFIGDATA fi if [[ -d $PROGRAMDATA/logs ]]; then mv $PROGRAMDATA/logs $LOGDATA fi + if [[ -d $PROGRAMDATA/logs ]]; then + mv $PROGRAMDATA/cache $CACHEDATA + fi ;; abort-upgrade) diff --git a/deployment/debian-package-x64/pkg-src/prerm b/deployment/debian-package-x64/pkg-src/prerm index 4770c03c4..e965cb7d7 100644 --- a/deployment/debian-package-x64/pkg-src/prerm +++ b/deployment/debian-package-x64/pkg-src/prerm @@ -12,7 +12,8 @@ fi # Data directories for program data (cache, db), configs, and logs PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} -LOGDATA=${JELLYFIN_DATA_DIRECTORY-/var/log/$NAME} +LOGDATA=${JELLYFIN_LOG_DIRECTORY-/var/log/$NAME} +CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME} case "$1" in remove|upgrade|deconfigure) diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile index e5deac29f..8bb1d527d 100644 --- a/deployment/fedora-package-x64/Dockerfile +++ b/deployment/fedora-package-x64/Dockerfile @@ -1,15 +1,27 @@ FROM fedora:29 -ARG HOME=/build -RUN mkdir /build && \ - dnf install -y @buildsys-build rpmdevtools dnf-plugins-core && \ - dnf copr enable -y @dotnet-sig/dotnet && \ - rpmdev-setuptree +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64 +ARG ARTIFACT_DIR=/dist +ARG SDK_VERSION=2.2 +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist -WORKDIR /build/rpmbuild -COPY ./deployment/fedora-package-x64/pkg-src/jellyfin.spec SPECS -COPY ./deployment/fedora-package-x64/pkg-src/ SOURCES +# Prepare Fedora build environment +RUN dnf update -y \ + && dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel \ + && dnf copr enable -y @dotnet-sig/dotnet \ + && rpmdev-setuptree \ + && dnf install -y dotnet-sdk-${SDK_VERSION} \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR}/SPECS \ + && ln -s ${PLATFORM_DIR}/pkg-src/jellyfin.spec ${SOURCE_DIR}/SPECS/jellyfin.spec \ + && mkdir -p ${SOURCE_DIR}/SOURCES \ + && ln -s ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/SOURCES -RUN spectool -g -R SPECS/jellyfin.spec && \ - rpmbuild -bs SPECS/jellyfin.spec && \ - dnf build-dep -y SRPMS/jellyfin-*.src.rpm && \ - rpmbuild -bb SPECS/jellyfin.spec; \ No newline at end of file +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/fedora-package-x64/clean.sh b/deployment/fedora-package-x64/clean.sh index d7233208f..408167e49 100755 --- a/deployment/fedora-package-x64/clean.sh +++ b/deployment/fedora-package-x64/clean.sh @@ -2,17 +2,33 @@ source ../common.build.sh -VERSION=`get_version ../..` +keep_artifacts="${1}" -package_temporary_dir="`pwd`/pkg-dist-tmp" -pkg_src_dir="`pwd`/pkg-src" -image_name="jellyfin-rpmbuild" -docker_sudo="" -if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \ - [ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then - docker_sudo=sudo +WORKDIR="$( pwd )" +VERSION="$( grep -A1 '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +package_source_dir="${WORKDIR}/pkg-src" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-fedora-build" + +rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null \ + || sudo rm -f "${package_source_dir}/jellyfin-${VERSION}.tar.gz" &>/dev/null + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force fi - -$docker_sudo docker image rm $image_name --force -rm -rf "$package_temporary_dir" -rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh new file mode 100755 index 000000000..3acf1ec0d --- /dev/null +++ b/deployment/fedora-package-x64/docker-build.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# Builds the RPM inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +ls -al SOURCES/pkg-src/ + +# Build RPM +spectool -g -R SPECS/jellyfin.spec +rpmbuild -bs SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" +rpmbuild -bb SPECS/jellyfin.spec --define "_sourcedir ${SOURCE_DIR}/SOURCES/pkg-src/" + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/rpm +mv /root/rpmbuild/RPMS/x86_64/jellyfin-*.rpm /root/rpmbuild/SRPMS/jellyfin-*.src.rpm ${ARTIFACT_DIR}/rpm/ diff --git a/deployment/fedora-package-x64/package.sh b/deployment/fedora-package-x64/package.sh index d459cdb24..74586417d 100755 --- a/deployment/fedora-package-x64/package.sh +++ b/deployment/fedora-package-x64/package.sh @@ -1,38 +1,29 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash source ../common.build.sh -VERSION=`get_version ../..` +WORKDIR="$( pwd )" +VERSION="$( grep '^Version:' ${WORKDIR}/pkg-src/jellyfin.spec | awk '{ print $NF }' )" -# TODO get the version in the package automatically. And using the changelog to decide the debian package suffix version. +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +pkg_src_dir="${WORKDIR}/pkg-src" +current_user="$( whoami )" +image_name="jellyfin-fedora-build" -# Build a Jellyfin .rpm file with Docker on Linux -# Places the output .rpm file in the parent directory - -set -o errexit -set -o xtrace -set -o nounset - -package_temporary_dir="`pwd`/pkg-dist-tmp" -output_dir="`pwd`/pkg-dist" -pkg_src_dir="`pwd`/pkg-src" -current_user="`whoami`" -image_name="jellyfin-rpmbuild" -docker_sudo="" -if ! $(id -Gn | grep -q 'docker') && [ ! ${EUID:-1000} -eq 0 ] && \ - [ ! $USER == "root" ] && ! $(echo "$OSTYPE" | grep -q "darwin"); then - docker_sudo=sudo +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" fi -cleanup() { - set +o errexit - $docker_sudo docker image rm $image_name --force - rm -rf "$package_temporary_dir" - rm -rf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -} -trap cleanup EXIT INT +# Create RPM source archive GNU_TAR=1 -mkdir -p "$package_temporary_dir" +mkdir -p "${package_temporary_dir}" echo "Bundling all sources for RPM build." tar \ --transform "s,^\.,jellyfin-${VERSION}," \ @@ -47,12 +38,12 @@ tar \ --exclude='**/.nuget' \ --exclude='*.deb' \ --exclude='*.rpm' \ --zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" \ +-czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" \ -C "../.." ./ || GNU_TAR=0 if [ $GNU_TAR -eq 0 ]; then echo "The installed tar binary did not support --transform. Using workaround." - mkdir -p "$package_temporary_dir/jellyfin-${VERSION}" + mkdir -p "${package_temporary_dir}/jellyfin" # Not GNU tar tar \ --exclude='.git*' \ @@ -67,20 +58,23 @@ if [ $GNU_TAR -eq 0 ]; then --exclude='*.deb' \ --exclude='*.rpm' \ -zcf \ - "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" \ - -C "../.." \ - ./ + "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \ + -C "../.." ./ echo "Extracting filtered package." - tar -xzf "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" -C "$package_temporary_dir/jellyfin-${VERSION}" + tar -xzf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}/jellyfin-${VERSION}" echo "Removing filtered package." - rm "$package_temporary_dir/jellyfin-${VERSION}/jellyfin.tar.gz" + rm -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" echo "Repackaging package into final tarball." - tar -zcf "$pkg_src_dir/jellyfin-${VERSION}.tar.gz" -C "$package_temporary_dir" "jellyfin-${VERSION}" + tar -czf "${pkg_src_dir}/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}" fi -$docker_sudo docker build ../.. -t "$image_name" -f ./Dockerfile -mkdir -p "$output_dir" -$docker_sudo docker run --rm -v "$package_temporary_dir:/temp" "$image_name" sh -c 'find /build/rpmbuild -maxdepth 3 -type f -name "jellyfin*.rpm" -exec mv {} /temp \;' -chown -R "$current_user" "$package_temporary_dir" \ -|| sudo chown -R "$current_user" "$package_temporary_dir" -mv "$package_temporary_dir"/*.rpm "$output_dir" +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the RPMs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the RPMs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" +# Move the RPMs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/rpm/* "${output_dir}" diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/deployment/fedora-package-x64/pkg-src/jellyfin.env index 827a33f46..abfa670e4 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.env @@ -14,14 +14,22 @@ # General options # -# Tell jellyfin wich ffmpeg/ffprobe to use -# JELLYFIN_FFMPEG="-ffmpeg /usr/bin/ffmpeg -ffprobe /usr/bin/ffprobe" - # Program directories JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin" JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin" JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" +JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin" + # In-App service control -JELLYFIN_RESTART_OPT="-restartpath /usr/libexec/jellyfin/restart.sh" -# Additional options for the binary -JELLYFIN_ADD_OPTS="" \ No newline at end of file +JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" + +# [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values +#JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" +#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" + +# [OPTIONAL] run Jellyfin as a headless service +#JELLYFIN_SERVICE_OPT="--service" + +# [OPTIONAL] run Jellyfin without the web app +#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp" + diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/deployment/fedora-package-x64/pkg-src/jellyfin.service index 0ece5b57f..d58df9d94 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin -programdata ${JELLYFIN_DATA_DIRECTORY} -configdir ${JELLYFIN_CONFIG_DIRECTORY} -logdir ${JELLYFIN_LOG_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_ADD_OPTS} ${JELLYFIN_FFMPEG} +ExecStart=/usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec index 6a4a5870b..851c40044 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec @@ -1,11 +1,11 @@ %global debug_package %{nil} -# jellyfin tag to package -%global gittag v10.1.0 -# Taglib-sharp commit of the submodule since github archive doesn't include submodules -%global taglib_commit ee5ab21742b71fd1b87ee24895582327e9e04776 -%global taglib_shortcommit %(c=%{taglib_commit}; echo ${c:0:7}) +# Set the dotnet runtime +%if 0%{?fedora} +%global dotnet_runtime fedora-x64 +%else +%global dotnet_runtime centos-x64 +%endif -AutoReq: no Name: jellyfin Version: 10.1.0 Release: 1%{?dist} @@ -31,13 +31,11 @@ BuildRequires: dotnet-sdk-2.2 # RPMfusion free Requires: ffmpeg -# For the update-db-paths.sh script to fix emby paths to jellyfin -%{?fedora:Recommends: sqlite} - # Fedora has openssl1.1 which is incompatible with dotnet %{?fedora:Requires: compat-openssl10} -# Disable Automatic Dependency Processing for Centos -%{?el7:AutoReqProv: no} + +# Disable Automatic Dependency Processing +AutoReqProv: no %description Jellyfin is a free software media system that puts you in control of managing and streaming your media. @@ -51,7 +49,7 @@ Jellyfin is a free software media system that puts you in control of managing an %install export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 -dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime fedora-x64 Jellyfin.Server +dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} Jellyfin.Server %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/%{name}/LICENSE %{__install} -D -m 0644 %{SOURCE5} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/%{name}/logging.json @@ -63,6 +61,7 @@ EOF %{__mkdir} -p %{buildroot}%{_sharedstatedir}/jellyfin %{__mkdir} -p %{buildroot}%{_sysconfdir}/%{name} %{__mkdir} -p %{buildroot}%{_var}/log/jellyfin +%{__mkdir} -p %{buildroot}%{_var}/cache/jellyfin %{__install} -D -m 0644 %{SOURCE1} %{buildroot}%{_unitdir}/%{name}.service %{__install} -D -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name} @@ -90,8 +89,9 @@ EOF %config(noreplace) %attr(600,root,root) %{_sysconfdir}/sudoers.d/%{name}-sudoers %config(noreplace) %{_sysconfdir}/systemd/system/%{name}.service.d/override.conf %config(noreplace) %attr(644,jellyfin,jellyfin) %{_sysconfdir}/%{name}/logging.json -%attr(-,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin +%attr(750,jellyfin,jellyfin) %dir %{_sharedstatedir}/jellyfin %attr(-,jellyfin,jellyfin) %dir %{_var}/log/jellyfin +%attr(750,jellyfin,jellyfin) %dir %{_var}/cache/jellyfin %if 0%{?fedora} %license LICENSE %else @@ -106,7 +106,7 @@ getent passwd jellyfin >/dev/null || \ exit 0 %post -# Move existing configuration to /etc/jellyfin and symlink config to /etc/jellyfin +# Move existing configuration cache and logs to their new locations and symlink them. if [ $1 -gt 1 ] ; then service_state=$(systemctl is-active jellyfin.service) if [ "${service_state}" = "active" ]; then @@ -122,6 +122,11 @@ if [ $1 -gt 1 ] ; then rmdir %{_sharedstatedir}/%{name}/logs ln -sf %{_var}/log/jellyfin %{_sharedstatedir}/%{name}/logs fi + if [ ! -L %{_sharedstatedir}/%{name}/cache ]; then + mv %{_sharedstatedir}/%{name}/cache/* %{_var}/cache/jellyfin + rmdir %{_sharedstatedir}/%{name}/cache + ln -sf %{_var}/cache/jellyfin %{_sharedstatedir}/%{name}/cache + fi if [ "${service_state}" = "active" ]; then systemctl start jellyfin.service fi diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers index b31d52f7e..dd245af4b 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.sudoers @@ -4,16 +4,16 @@ Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemctl start jellyfin, /bin/systemc Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemctl stop jellyfin, /bin/systemctl stop jellyfin -%jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD -%jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD +jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD Defaults!RESTARTSERVER_SYSTEMD !requiretty Defaults!STARTSERVER_SYSTEMD !requiretty Defaults!STOPSERVER_SYSTEMD !requiretty -# Uncomment to allow the server to mount iso images -# %jellyfin ALL=(ALL) NOPASSWD: /bin/mount -# %jellyfin ALL=(ALL) NOPASSWD: /bin/umount +# Allow the server to mount iso images +jellyfin ALL=(ALL) NOPASSWD: /bin/mount +jellyfin ALL=(ALL) NOPASSWD: /bin/umount -Defaults:%jellyfin !requiretty +Defaults:jellyfin !requiretty diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile new file mode 100644 index 000000000..485b6c42c --- /dev/null +++ b/deployment/ubuntu-package-x64/Dockerfile @@ -0,0 +1,21 @@ +FROM microsoft/dotnet:2.2-sdk-bionic +# Docker build arguments +ARG SOURCE_DIR=/jellyfin +ARG PLATFORM_DIR=/jellyfin/deployment/ubuntu-package-x64 +ARG ARTIFACT_DIR=/dist +# Docker run environment +ENV SOURCE_DIR=/jellyfin +ENV ARTIFACT_DIR=/dist +ENV DEB_BUILD_OPTIONS=noddebs + +# Prepare Ubuntu build environment +RUN apt-get update \ + && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev \ + && ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \ + && mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian + +VOLUME ${ARTIFACT_DIR}/ + +COPY . ${SOURCE_DIR}/ + +ENTRYPOINT ["/docker-build.sh"] diff --git a/deployment/ubuntu-package-x64/clean.sh b/deployment/ubuntu-package-x64/clean.sh new file mode 100755 index 000000000..c92c7fdec --- /dev/null +++ b/deployment/ubuntu-package-x64/clean.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +keep_artifacts="${1}" + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +rm -rf "${package_temporary_dir}" &>/dev/null \ + || sudo rm -rf "${package_temporary_dir}" &>/dev/null + +rm -rf "${output_dir}" &>/dev/null \ + || sudo rm -rf "${output_dir}" &>/dev/null + +if [[ ${keep_artifacts} == 'n' ]]; then + docker_sudo="" + if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo=sudo + fi + ${docker_sudo} docker image rm ${image_name} --force +fi diff --git a/deployment/ubuntu-package-x64/dependencies.txt b/deployment/ubuntu-package-x64/dependencies.txt new file mode 100644 index 000000000..bdb967096 --- /dev/null +++ b/deployment/ubuntu-package-x64/dependencies.txt @@ -0,0 +1 @@ +docker diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh new file mode 100755 index 000000000..0590be097 --- /dev/null +++ b/deployment/ubuntu-package-x64/docker-build.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Builds the DEB inside the Docker container + +set -o errexit +set -o xtrace + +# Move to source directory +pushd ${SOURCE_DIR} + +# Remove build-dep for dotnet-sdk-2.2, since it's not a package in this image +sed -i '/dotnet-sdk-2.2,/d' debian/control + +# Build DEB +dpkg-buildpackage -us -uc + +# Move the artifacts out +mkdir -p ${ARTIFACT_DIR}/deb +mv /jellyfin_* ${ARTIFACT_DIR}/deb/ diff --git a/deployment/ubuntu-package-x64/package.sh b/deployment/ubuntu-package-x64/package.sh new file mode 100755 index 000000000..6d4625a19 --- /dev/null +++ b/deployment/ubuntu-package-x64/package.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +source ../common.build.sh + +WORKDIR="$( pwd )" + +package_temporary_dir="${WORKDIR}/pkg-dist-tmp" +output_dir="${WORKDIR}/pkg-dist" +current_user="$( whoami )" +image_name="jellyfin-ubuntu-build" + +# Determine if sudo should be used for Docker +if [[ ! -z $(id -Gn | grep -q 'docker') ]] \ + && [[ ! ${EUID:-1000} -eq 0 ]] \ + && [[ ! ${USER} == "root" ]] \ + && [[ ! -z $( echo "${OSTYPE}" | grep -q "darwin" ) ]]; then + docker_sudo="sudo" +else + docker_sudo="" +fi + +# Set up the build environment Docker image +${docker_sudo} docker build ../.. -t "${image_name}" -f ./Dockerfile +# Build the DEBs and copy out to ${package_temporary_dir} +${docker_sudo} docker run --rm -v "${package_temporary_dir}:/dist" "${image_name}" +# Correct ownership on the DEBs (as current user, then as root if that fails) +chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null \ + || sudo chown -R "${current_user}" "${package_temporary_dir}" &>/dev/null +# Move the DEBs to the output directory +mkdir -p "${output_dir}" +mv "${package_temporary_dir}"/deb/* "${output_dir}" diff --git a/deployment/ubuntu-package-x64/pkg-src b/deployment/ubuntu-package-x64/pkg-src new file mode 120000 index 000000000..4c695fea1 --- /dev/null +++ b/deployment/ubuntu-package-x64/pkg-src @@ -0,0 +1 @@ +../debian-package-x64/pkg-src \ No newline at end of file diff --git a/deployment/win-generic/install-jellyfin.ps1 b/deployment/win-generic/install-jellyfin.ps1 index 56c098462..b6e00e056 100644 --- a/deployment/win-generic/install-jellyfin.ps1 +++ b/deployment/win-generic/install-jellyfin.ps1 @@ -93,12 +93,12 @@ if($Quiet.IsPresent -or $Quiet -eq $true){ Copy-Item -Path $PSScriptRoot/* -DestinationPath "$Script:DefaultJellyfinInstallDirectory/" -Force -Recurse if($Script:InstallAsService){ if($Script:InstallServiceAsUser){ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" -programdata `"$Script:JellyfinDataDir`" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" Start-Sleep -Milliseconds 500 &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START }else{ - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" -programdata `"$Script:JellyfinDataDir`" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" Start-Sleep -Milliseconds 500 #&"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin ObjectName $Script:UserCredentials.UserName $Script:UserCredentials.GetNetworkCredential().Password #Set-Service -Name Jellyfin -Credential $Script:UserCredentials @@ -171,13 +171,13 @@ function InstallJellyfin { if($Script:InstallAsService){ if($Script:InstallServiceAsUser){ Write-Host "Installing Service as user $($Script:UserCredentials.UserName)" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" -programdata `"$Script:JellyfinDataDir`" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" Start-Sleep -Milliseconds 2000 &sc.exe config Jellyfin obj=".\$($Script:UserCredentials.UserName)" password="$($Script:UserCredentials.GetNetworkCredential().Password)" &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START }else{ Write-Host "Installing Service as LocalSystem" - &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" -programdata `"$Script:JellyfinDataDir`" + &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" install Jellyfin `"$Script:DefaultJellyfinInstallDirectory\jellyfin.exe`" --datadir `"$Script:JellyfinDataDir`" Start-Sleep -Milliseconds 2000 &"$Script:DefaultJellyfinInstallDirectory\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START } @@ -457,4 +457,4 @@ $StartProgramCheck.Add_CheckedChanged({StartJellyFinBoxCheckChanged}) #endregion GUI } -[void]$InstallForm.ShowDialog() \ No newline at end of file +[void]$InstallForm.ShowDialog()