aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines.yml2
-rw-r--r--.copr/Makefile67
-rw-r--r--.github/stale.yml13
-rw-r--r--.gitignore3
-rw-r--r--Dockerfile8
-rw-r--r--Dockerfile.arm8
-rw-r--r--Dockerfile.arm648
-rw-r--r--Emby.Dlna/Api/DlnaServerService.cs33
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj2
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs2
-rw-r--r--Emby.Dlna/Ssdp/DeviceDiscovery.cs23
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj7
-rw-r--r--Emby.Naming/Common/NamingOptions.cs29
-rw-r--r--Emby.Naming/Emby.Naming.csproj8
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj2
-rw-r--r--Emby.Photos/Emby.Photos.csproj2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs44
-rw-r--r--Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs11
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj2
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs259
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs13
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs7
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs4
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs13
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs28
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json38
-rw-r--r--Emby.Server.Implementations/Localization/Core/cs.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/hu.json38
-rw-r--r--Emby.Server.Implementations/Localization/Core/nl.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-CN.json2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs8
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs24
-rw-r--r--Emby.Server.Implementations/ServerApplicationPaths.cs20
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs242
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj2
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs13
-rw-r--r--MediaBrowser.Api/BaseApiService.cs18
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs7
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj2
-rw-r--r--MediaBrowser.Api/PackageService.cs33
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs10
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs110
-rw-r--r--MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs6
-rw-r--r--MediaBrowser.Api/Session/SessionsService.cs2
-rw-r--r--MediaBrowser.Common/Cryptography/PasswordHash.cs11
-rw-r--r--MediaBrowser.Common/Extensions/CollectionExtensions.cs48
-rw-r--r--MediaBrowser.Common/Extensions/CopyToExtensions.cs26
-rw-r--r--MediaBrowser.Common/Hex.cs94
-rw-r--r--MediaBrowser.Common/HexHelper.cs24
-rw-r--r--MediaBrowser.Common/IApplicationHost.cs8
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj2
-rw-r--r--MediaBrowser.Common/Updates/IInstallationManager.cs85
-rw-r--r--MediaBrowser.Controller/Entities/ICollectionFolder.cs3
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs16
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs4
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs3
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj2
-rw-r--r--MediaBrowser.Model/Globalization/ILocalizationManager.cs4
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj3
-rw-r--r--MediaBrowser.Model/Net/SocketReceiveResult.cs2
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs4
-rw-r--r--MediaBrowser.Model/Updates/PackageInfo.cs3
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj2
-rw-r--r--MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs1
-rw-r--r--MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs118
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs8
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj2
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj2
-rw-r--r--MediaBrowser.sln13
-rw-r--r--Mono.Nat/AbstractNatDevice.cs55
-rw-r--r--Mono.Nat/Enums/ProtocolType.cs36
-rw-r--r--Mono.Nat/EventArgs/DeviceEventArgs.cs45
-rw-r--r--Mono.Nat/INatDevice.cs45
-rw-r--r--Mono.Nat/ISearcher.cs46
-rw-r--r--Mono.Nat/Mapping.cs121
-rw-r--r--Mono.Nat/Mono.Nat.csproj17
-rw-r--r--Mono.Nat/NatManager.cs86
-rw-r--r--Mono.Nat/NatProtocol.cs8
-rw-r--r--Mono.Nat/Pmp/PmpConstants.cs56
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs217
-rw-r--r--Mono.Nat/Pmp/PmpSearcher.cs235
-rw-r--r--Mono.Nat/Properties/AssemblyInfo.cs21
-rw-r--r--Mono.Nat/Upnp/Messages/GetServicesMessage.cs64
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs75
-rw-r--r--Mono.Nat/Upnp/Messages/UpnpMessage.cs84
-rw-r--r--Mono.Nat/Upnp/Searchers/UpnpSearcher.cs111
-rw-r--r--Mono.Nat/Upnp/UpnpNatDevice.cs267
-rw-r--r--RSSDP/RSSDP.csproj2
-rw-r--r--benches/Jellyfin.Common.Benches/HexDecodeBenches.cs45
-rw-r--r--benches/Jellyfin.Common.Benches/HexEncodeBenches.cs32
-rw-r--r--benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj16
-rw-r--r--benches/Jellyfin.Common.Benches/Program.cs14
-rw-r--r--deployment/centos-package-x64/Dockerfile11
-rwxr-xr-xdeployment/centos-package-x64/docker-build.sh71
-rw-r--r--deployment/debian-package-arm64/Dockerfile.amd648
-rw-r--r--deployment/debian-package-arm64/Dockerfile.arm642
-rwxr-xr-xdeployment/debian-package-arm64/docker-build.sh14
-rw-r--r--deployment/debian-package-armhf/Dockerfile.amd648
-rw-r--r--deployment/debian-package-armhf/Dockerfile.armhf8
-rwxr-xr-xdeployment/debian-package-armhf/docker-build.sh14
-rw-r--r--deployment/debian-package-x64/Dockerfile8
-rwxr-xr-xdeployment/debian-package-x64/docker-build.sh14
-rw-r--r--deployment/debian-package-x64/pkg-src/control4
-rwxr-xr-x[-rw-r--r--]deployment/debian-package-x64/pkg-src/rules15
-rw-r--r--deployment/fedora-package-x64/Dockerfile8
-rwxr-xr-xdeployment/fedora-package-x64/docker-build.sh69
-rw-r--r--deployment/fedora-package-x64/pkg-src/jellyfin.spec59
-rw-r--r--deployment/ubuntu-package-arm64/Dockerfile.amd6412
-rw-r--r--deployment/ubuntu-package-arm64/Dockerfile.arm646
-rwxr-xr-xdeployment/ubuntu-package-arm64/docker-build.sh14
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.amd6412
-rw-r--r--deployment/ubuntu-package-armhf/Dockerfile.armhf8
-rwxr-xr-xdeployment/ubuntu-package-armhf/docker-build.sh14
-rw-r--r--deployment/ubuntu-package-x64/Dockerfile8
-rwxr-xr-xdeployment/ubuntu-package-x64/docker-build.sh14
-rw-r--r--deployment/windows/build-jellyfin.ps122
-rw-r--r--deployment/windows/dialogs/setuptype.nsddef12
-rw-r--r--deployment/windows/dialogs/setuptype.nsdinc50
-rw-r--r--deployment/windows/jellyfin.nsi140
-rw-r--r--tests/Jellyfin.Common.Tests/HexTests.cs19
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/PasswordHashTests.cs6
-rw-r--r--tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs55
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj19
129 files changed, 1360 insertions, 2747 deletions
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 46c478b08..c829da98a 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -245,7 +245,7 @@ jobs:
inputs:
targetType: 'filePath' # Optional. Options: filePath, inline
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
- arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
+ arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
#failOnStderr: false # Optional
diff --git a/.copr/Makefile b/.copr/Makefile
index 84b98a011..ba330ada9 100644
--- a/.copr/Makefile
+++ b/.copr/Makefile
@@ -1,8 +1,59 @@
-srpm:
- dnf -y install git
- git submodule update --init --recursive
- cd deployment/fedora-package-x64; \
- ./create_tarball.sh; \
- rpmbuild -bs pkg-src/jellyfin.spec \
- --define "_sourcedir $$PWD/pkg-src/" \
- --define "_srcrpmdir $(outdir)"
+VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
+ deployment/fedora-package-x64/pkg-src/jellyfin.spec)
+
+deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
+ curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
+ https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
+ || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
+ https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
+
+srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
+ cd deployment/fedora-package-x64; \
+ SOURCE_DIR=../.. \
+ WORKDIR="$${PWD}"; \
+ package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
+ pkg_src_dir="$${WORKDIR}/pkg-src"; \
+ GNU_TAR=1; \
+ 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/jellyfin-$(VERSION).tar.gz" \
+ -C $${SOURCE_DIR} ./ || GNU_TAR=0; \
+ if [ $${GNU_TAR} -eq 0 ]; then \
+ package_temporary_dir="$$(mktemp -d)"; \
+ mkdir -p "$${package_temporary_dir}/jellyfin"; \
+ tar \
+ --exclude='.git*' \
+ --exclude='**/.git' \
+ --exclude='**/.hg' \
+ --exclude='**/.vs' \
+ --exclude='**/.vscode' \
+ --exclude='deployment' \
+ --exclude='**/bin' \
+ --exclude='**/obj' \
+ --exclude='**/.nuget' \
+ --exclude='*.deb' \
+ --exclude='*.rpm' \
+ -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
+ -C $${SOURCE_DIR} ./; \
+ mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
+ tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
+ -C "$${package_temporary_dir}/jellyfin-$(VERSION); \
+ rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
+ tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
+ -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
+ rm -rf $${package_temporary_dir}; \
+ fi; \
+ rpmbuild -bs pkg-src/jellyfin.spec \
+ --define "_sourcedir $$PWD/pkg-src/" \
+ --define "_srcrpmdir $(outdir)"
diff --git a/.github/stale.yml b/.github/stale.yml
index 9ea0e3796..05892c44d 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 90
+daysUntilStale: 120
# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 14
+daysUntilClose: 21
# Issues with these labels will never be considered stale
exemptLabels:
- regression
@@ -11,12 +11,15 @@ exemptLabels:
- future
- feature
- enhancement
+ - confirmed
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
- Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
- If this issue is safe to close now please do so.
- If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
+ This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
+
+ If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
+
+ This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false
diff --git a/.gitignore b/.gitignore
index 34cf1a84c..42243f01a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -268,3 +268,6 @@ doc/
# Deployment artifacts
dist
*.exe
+
+# BenchmarkDotNet artifacts
+BenchmarkDotNet.Artifacts
diff --git a/Dockerfile b/Dockerfile
index c24c32181..2a60bf184 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -42,7 +42,7 @@ ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT ./jellyfin/jellyfin \
- --datadir /config \
- --cachedir /cache \
- --ffmpeg /usr/local/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/local/bin/ffmpeg"]
diff --git a/Dockerfile.arm b/Dockerfile.arm
index f8c8511ae..fd3d1e070 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -38,7 +38,7 @@ ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT ./jellyfin/jellyfin \
- --datadir /config \
- --cachedir /cache \
- --ffmpeg /usr/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/bin/ffmpeg"]
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 9b343659f..3c1b2e3ea 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -38,7 +38,7 @@ ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT ./jellyfin/jellyfin \
- --datadir /config \
- --cachedir /cache \
- --ffmpeg /usr/bin/ffmpeg
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/bin/ffmpeg"]
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index 8bf3797f8..1f137e620 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Emby.Dlna.Main;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
@@ -108,12 +109,13 @@ namespace Emby.Dlna.Api
public class DlnaServerService : IService, IRequiresRequest
{
- private readonly IDlnaManager _dlnaManager;
-
private const string XMLContentType = "text/xml; charset=UTF-8";
+ private readonly IDlnaManager _dlnaManager;
+ private readonly IHttpResultFactory _resultFactory;
+ private readonly IServerConfigurationManager _configurationManager;
+
public IRequest Request { get; set; }
- private IHttpResultFactory _resultFactory;
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
@@ -121,10 +123,14 @@ namespace Emby.Dlna.Api
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
- public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
+ public DlnaServerService(
+ IDlnaManager dlnaManager,
+ IHttpResultFactory httpResultFactory,
+ IServerConfigurationManager configurationManager)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
+ _configurationManager = configurationManager;
}
private string GetHeader(string name)
@@ -205,14 +211,25 @@ namespace Emby.Dlna.Api
var pathInfo = Parse(Request.PathInfo);
var first = pathInfo[0];
+ string baseUrl = _configurationManager.Configuration.BaseUrl;
+
// backwards compatibility
- // TODO: Work out what this is doing.
- if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
+ if (baseUrl.Length == 0
+ && (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)))
{
index++;
}
+ else if (string.Equals(first, baseUrl.Remove(0, 1)))
+ {
+ index++;
+ var second = pathInfo[1];
+ if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
+ {
+ index++;
+ }
+ }
return pathInfo[index];
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 34b49120b..8d6fabdb4 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -12,7 +12,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index a3a013096..2ca44b7ea 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -160,7 +160,7 @@ namespace Emby.Dlna.PlayTo
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
- var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, null, uri.OriginalString, null);
+ var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
diff --git a/Emby.Dlna/Ssdp/DeviceDiscovery.cs b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
index 298f68a28..c5f3593da 100644
--- a/Emby.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/Emby.Dlna/Ssdp/DeviceDiscovery.cs
@@ -4,8 +4,6 @@ using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
using Rssdp;
using Rssdp.Infrastructure;
@@ -15,13 +13,14 @@ namespace Emby.Dlna.Ssdp
{
private bool _disposed;
- private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscoveredInternal;
private int _listenerCount;
private object _syncLock = new object();
+
+ /// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered
{
add
@@ -31,6 +30,7 @@ namespace Emby.Dlna.Ssdp
_listenerCount++;
DeviceDiscoveredInternal += value;
}
+
StartInternal();
}
remove
@@ -43,21 +43,16 @@ namespace Emby.Dlna.Ssdp
}
}
+ /// <inheritdoc />
public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
private SsdpDeviceLocator _deviceLocator;
- private readonly ISocketFactory _socketFactory;
private ISsdpCommunicationsServer _commsServer;
- public DeviceDiscovery(
- ILoggerFactory loggerFactory,
- IServerConfigurationManager config,
- ISocketFactory socketFactory)
+ public DeviceDiscovery(IServerConfigurationManager config)
{
- _logger = loggerFactory.CreateLogger(nameof(DeviceDiscovery));
_config = config;
- _socketFactory = socketFactory;
}
// Call this method from somewhere in your code to start the search.
@@ -82,8 +77,8 @@ namespace Emby.Dlna.Ssdp
//_DeviceLocator.NotificationFilter = "upnp:rootdevice";
// Connect our event handler so we process devices as they are found
- _deviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
- _deviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
+ _deviceLocator.DeviceAvailable += OnDeviceLocatorDeviceAvailable;
+ _deviceLocator.DeviceUnavailable += OnDeviceLocatorDeviceUnavailable;
var dueTime = TimeSpan.FromSeconds(5);
var interval = TimeSpan.FromSeconds(_config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds);
@@ -94,7 +89,7 @@ namespace Emby.Dlna.Ssdp
}
// Process each found device in the event handler
- void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
+ private void OnDeviceLocatorDeviceAvailable(object sender, DeviceAvailableEventArgs e)
{
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
@@ -115,7 +110,7 @@ namespace Emby.Dlna.Ssdp
DeviceDiscoveredInternal?.Invoke(this, args);
}
- private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
+ private void OnDeviceLocatorDeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
{
var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 2e539f2c7..85cecdc44 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
@@ -17,9 +17,4 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
- <PropertyGroup>
- <!-- We need at least C# 7.1 for the "default literal" feature-->
- <LangVersion>latest</LangVersion>
- </PropertyGroup>
-
</Project>
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index 88a9b46e6..d37be0e63 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -311,6 +311,14 @@ namespace Emby.Naming.Common
}
},
+ // This isn't a Kodi naming rule, but the expression below causes false positives,
+ // so we make sure this one gets tested first.
+ // "Foo Bar 889"
+ new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>(\w+\s*?)*)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
+ {
+ IsNamed = true
+ },
+
new EpisodeExpression("[\\\\/\\._ \\[\\(-]([0-9]+)x([0-9]+(?:(?:[a-i]|\\.[1-9])(?![0-9]))?)([^\\\\/]*)$")
{
SupportsAbsoluteEpisodeNumbers = true
@@ -328,28 +336,33 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
- new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$")
+                // [bar] Foo - 1 [baz]
+ new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>(\w+\s*?)+?)[-\s_]+(?<epnumber>\d+).*$")
+ {
+ IsNamed = true
+ },
+ new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
{
IsNamed = true
},
- new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$")
+ new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
{
IsNamed = true
},
// "01.avi"
- new EpisodeExpression(@".*[\\\/](?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.\w+$")
+ new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
{
IsOptimistic = true,
IsNamed = true
@@ -654,9 +667,9 @@ namespace Emby.Naming.Common
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
}.Select(i => new EpisodeExpression(i)
- {
- IsNamed = true
- }).ToArray();
+ {
+ IsNamed = true
+ }).ToArray();
VideoFileExtensions = extensions
.Distinct(StringComparer.OrdinalIgnoreCase)
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index a23fa3df7..fd0773df5 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -23,10 +23,10 @@
<!-- Code analysers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" />
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.4" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index cbd3bde4f..004ded77b 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index b57b93a8c..a71c75127 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -14,7 +14,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index f7fe2bd63..120a5adc4 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -410,13 +410,17 @@ namespace Emby.Server.Implementations
_validAddressResults.Clear();
}
- public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
+ /// <inheritdoc />
+ public Version ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version;
+
+ /// <inheritdoc />
+ public string ApplicationVersionString { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3);
/// <summary>
/// Gets the current application user agent.
/// </summary>
/// <value>The application user agent.</value>
- public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersion;
+ public string ApplicationUserAgent => Name.Replace(' ', '-') + "/" + ApplicationVersionString;
/// <summary>
/// Gets the email address for use within a comment section of a user agent field.
@@ -452,7 +456,7 @@ namespace Emby.Server.Implementations
public string Name => ApplicationProductName;
/// <summary>
- /// Creates an instance of type and resolves all constructor dependencies
+ /// Creates an instance of type and resolves all constructor dependencies.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.Object.</returns>
@@ -460,7 +464,7 @@ namespace Emby.Server.Implementations
=> ActivatorUtilities.CreateInstance(_serviceProvider, type);
/// <summary>
- /// Creates an instance of type and resolves all constructor dependencies
+ /// Creates an instance of type and resolves all constructor dependencies.
/// </summary>
/// /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns>
@@ -508,11 +512,12 @@ namespace Emby.Server.Implementations
/// <inheritdoc />
public IReadOnlyCollection<T> GetExports<T>(bool manageLifetime = true)
{
+ // Convert to list so this isn't executed for each iteration
var parts = GetExportTypes<T>()
.Select(CreateInstanceSafe)
.Where(i => i != null)
.Cast<T>()
- .ToList(); // Convert to list so this isn't executed for each iteration
+ .ToList();
if (manageLifetime)
{
@@ -749,7 +754,8 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
- serviceCollection.AddSingleton(typeof(ICryptoProvider), typeof(CryptographyProvider));
+ var cryptoProvider = new CryptographyProvider();
+ serviceCollection.AddSingleton<ICryptoProvider>(cryptoProvider);
SocketFactory = new SocketFactory();
serviceCollection.AddSingleton(SocketFactory);
@@ -788,7 +794,17 @@ namespace Emby.Server.Implementations
_userRepository = GetUserRepository();
- UserManager = new UserManager(LoggerFactory.CreateLogger<UserManager>(), _userRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
+ UserManager = new UserManager(
+ LoggerFactory.CreateLogger<UserManager>(),
+ _userRepository,
+ XmlSerializer,
+ NetworkManager,
+ () => ImageProcessor,
+ () => DtoService,
+ this,
+ JsonSerializer,
+ FileSystemManager,
+ cryptoProvider);
serviceCollection.AddSingleton(UserManager);
@@ -866,8 +882,7 @@ namespace Emby.Server.Implementations
NotificationManager = new NotificationManager(LoggerFactory, UserManager, ServerConfigurationManager);
serviceCollection.AddSingleton(NotificationManager);
- serviceCollection.AddSingleton<IDeviceDiscovery>(
- new DeviceDiscovery(LoggerFactory, ServerConfigurationManager, SocketFactory));
+ serviceCollection.AddSingleton<IDeviceDiscovery>(new DeviceDiscovery(ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
serviceCollection.AddSingleton(ChapterManager);
@@ -1415,7 +1430,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Gets the system status.
/// </summary>
- /// <param name="cancellationToken">The cancellation token</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>SystemInfo.</returns>
public async Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken)
{
@@ -1425,7 +1440,7 @@ namespace Emby.Server.Implementations
{
HasPendingRestart = HasPendingRestart,
IsShuttingDown = IsShuttingDown,
- Version = ApplicationVersion,
+ Version = ApplicationVersionString,
WebSocketPortNumber = HttpPort,
CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(),
Id = SystemId,
@@ -1443,7 +1458,7 @@ namespace Emby.Server.Implementations
CanSelfRestart = CanSelfRestart,
CanLaunchWebBrowser = CanLaunchWebBrowser,
HasUpdateAvailable = HasUpdateAvailable,
- TranscodingTempPath = ApplicationPaths.TranscodingTempPath,
+ TranscodingTempPath = ApplicationPaths.TranscodePath,
ServerName = FriendlyName,
LocalAddress = localAddress,
SupportsLibraryMonitor = true,
@@ -1465,7 +1480,7 @@ namespace Emby.Server.Implementations
return new PublicSystemInfo
{
- Version = ApplicationVersion,
+ Version = ApplicationVersionString,
ProductName = ApplicationProductName,
Id = SystemId,
OperatingSystem = OperatingSystem.Id.ToString(),
@@ -1730,7 +1745,7 @@ namespace Emby.Server.Implementations
/// dns is prefixed with a valid Uri prefix.
/// </summary>
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
- /// <returns>The hostname in <paramref name="externalDns"/></returns>
+ /// <returns>The hostname in <paramref name="externalDns"/>.</returns>
private static string GetHostnameFromExternalDns(string externalDns)
{
if (string.IsNullOrEmpty(externalDns))
@@ -1844,6 +1859,7 @@ namespace Emby.Server.Implementations
internal class CertificateInfo
{
public string Path { get; set; }
+
public string Password { get; set; }
}
}
diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
index c7f92b80b..fe705cbe2 100644
--- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
+++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs
@@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.Configuration
{
var encodingConfig = this.GetConfiguration<EncodingOptions>("encoding");
- ((ServerApplicationPaths)ApplicationPaths).TranscodingTempPath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
+ ((ServerApplicationPaths)ApplicationPaths).TranscodePath = string.IsNullOrEmpty(encodingConfig.TranscodingTempPath) ?
null :
Path.Combine(encodingConfig.TranscodingTempPath, "transcodes");
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 33402f0e3..65f8a915f 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -3,10 +3,8 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Runtime.Serialization;
using System.Text;
using System.Text.Json;
-using System.Text.Json.Serialization;
using System.Threading;
using Emby.Server.Implementations.Playlists;
using MediaBrowser.Common.Json;
@@ -28,7 +26,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using SQLitePCL.pretty;
@@ -659,12 +656,14 @@ namespace Emby.Server.Implementations.Data
private void SaveItem(BaseItem item, BaseItem topParent, string userDataKey, IStatement saveItemStatement)
{
+ Type type = item.GetType();
+
saveItemStatement.TryBind("@guid", item.Id);
- saveItemStatement.TryBind("@type", item.GetType().FullName);
+ saveItemStatement.TryBind("@type", type.FullName);
- if (TypeRequiresDeserialization(item.GetType()))
+ if (TypeRequiresDeserialization(type))
{
- saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, _jsonOptions));
+ saveItemStatement.TryBind("@data", JsonSerializer.SerializeToUtf8Bytes(item, type, _jsonOptions));
}
else
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d6ca19e3f..45607dc09 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -10,7 +10,6 @@
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
- <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
<ProjectReference Include="..\MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj" />
<ProjectReference Include="..\Emby.Photos\Emby.Photos.csproj" />
@@ -32,6 +31,7 @@
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.0.0" />
+ <PackageReference Include="Mono.Nat" Version="2.0.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" />
<PackageReference Include="sharpcompress" Version="0.24.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" />
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index d55dc2f18..08041eb59 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
-using System.Globalization;
using System.Net;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Plugins;
@@ -15,179 +14,106 @@ using Mono.Nat;
namespace Emby.Server.Implementations.EntryPoints
{
+ /// <summary>
+ /// Server entrypoint handling external port forwarding.
+ /// </summary>
public class ExternalPortForwarding : IServerEntryPoint
{
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
+ private readonly object _createdRulesLock = new object();
+ private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
private Timer _timer;
+ private string _lastConfigIdentifier;
- private NatManager _natManager;
-
- private readonly object _createdRulesLock = new object();
- private List<string> _createdRules = new List<string>();
- private readonly object _usnsHandledLock = new object();
- private List<string> _usnsHandled = new List<string>();
+ private bool _disposed = false;
- public ExternalPortForwarding(ILoggerFactory loggerFactory, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ExternalPortForwarding"/> class.
+ /// </summary>
+ /// <param name="logger">The logger.</param>
+ /// <param name="appHost">The application host.</param>
+ /// <param name="config">The configuration manager.</param>
+ /// <param name="deviceDiscovery">The device discovery.</param>
+ public ExternalPortForwarding(
+ ILogger<ExternalPortForwarding> logger,
+ IServerApplicationHost appHost,
+ IServerConfigurationManager config,
+ IDeviceDiscovery deviceDiscovery)
{
- _logger = loggerFactory.CreateLogger("PortMapper");
+ _logger = logger;
_appHost = appHost;
_config = config;
_deviceDiscovery = deviceDiscovery;
- _httpClient = httpClient;
- _config.ConfigurationUpdated += _config_ConfigurationUpdated1;
}
- private void _config_ConfigurationUpdated1(object sender, EventArgs e)
- {
- _config_ConfigurationUpdated(sender, e);
- }
-
- private string _lastConfigIdentifier;
private string GetConfigIdentifier()
{
- var values = new List<string>();
+ const char Separator = '|';
var config = _config.Configuration;
- values.Add(config.EnableUPnP.ToString());
- values.Add(config.PublicPort.ToString(CultureInfo.InvariantCulture));
- values.Add(_appHost.HttpPort.ToString(CultureInfo.InvariantCulture));
- values.Add(_appHost.HttpsPort.ToString(CultureInfo.InvariantCulture));
- values.Add(_appHost.EnableHttps.ToString());
- values.Add((config.EnableRemoteAccess).ToString());
-
- return string.Join("|", values.ToArray());
+ return new StringBuilder(32)
+ .Append(config.EnableUPnP).Append(Separator)
+ .Append(config.PublicPort).Append(Separator)
+ .Append(_appHost.HttpPort).Append(Separator)
+ .Append(_appHost.HttpsPort).Append(Separator)
+ .Append(_appHost.EnableHttps).Append(Separator)
+ .Append(config.EnableRemoteAccess).Append(Separator)
+ .ToString();
}
- private async void _config_ConfigurationUpdated(object sender, EventArgs e)
+ private void OnConfigurationUpdated(object sender, EventArgs e)
{
if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
{
- DisposeNat();
+ Stop();
- await RunAsync();
+ Start();
}
}
+ /// <inheritdoc />
public Task RunAsync()
{
- if (_config.Configuration.EnableUPnP && _config.Configuration.EnableRemoteAccess)
- {
- Start();
- }
+ Start();
- _config.ConfigurationUpdated -= _config_ConfigurationUpdated;
- _config.ConfigurationUpdated += _config_ConfigurationUpdated;
+ _config.ConfigurationUpdated += OnConfigurationUpdated;
return Task.CompletedTask;
}
private void Start()
{
- _logger.LogDebug("Starting NAT discovery");
- if (_natManager == null)
+ if (!_config.Configuration.EnableUPnP || !_config.Configuration.EnableRemoteAccess)
{
- _natManager = new NatManager(_logger, _httpClient);
- _natManager.DeviceFound += NatUtility_DeviceFound;
- _natManager.StartDiscovery();
+ return;
}
+ _logger.LogDebug("Starting NAT discovery");
+
+ NatUtility.DeviceFound += OnNatUtilityDeviceFound;
+ NatUtility.StartDiscovery();
+
_timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
- _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
+ _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
}
- private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
+ private void Stop()
{
- if (_disposed)
- {
- return;
- }
-
- var info = e.Argument;
-
- if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
-
- if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
-
- // Filter device type
- if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
- nt.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
- usn.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
- nt.IndexOf("WANPPPConnection:", StringComparison.OrdinalIgnoreCase) == -1)
- {
- return;
- }
-
- var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
-
- if (info.Location == null)
- {
- return;
- }
-
- lock (_usnsHandledLock)
- {
- if (_usnsHandled.Contains(identifier))
- {
- return;
- }
-
- _usnsHandled.Add(identifier);
- }
-
- _logger.LogDebug("Found NAT device: " + identifier);
-
- if (IPAddress.TryParse(info.Location.Host, out var address))
- {
- // The Handle method doesn't need the port
- var endpoint = new IPEndPoint(address, info.Location.Port);
-
- IPAddress localAddress = null;
-
- try
- {
- var localAddressString = await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false);
-
- if (Uri.TryCreate(localAddressString, UriKind.Absolute, out var uri))
- {
- localAddressString = uri.Host;
-
- if (!IPAddress.TryParse(localAddressString, out localAddress))
- {
- return;
- }
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error");
- return;
- }
+ _logger.LogDebug("Stopping NAT discovery");
- if (_disposed)
- {
- return;
- }
+ NatUtility.StopDiscovery();
+ NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
- // This should never happen, but the Handle method will throw ArgumentNullException if it does
- if (localAddress == null)
- {
- return;
- }
+ _timer?.Dispose();
- var natManager = _natManager;
- if (natManager != null)
- {
- await natManager.Handle(localAddress, info, endpoint, NatProtocol.Upnp).ConfigureAwait(false);
- }
- }
+ _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void ClearCreatedRules(object state)
@@ -196,30 +122,24 @@ namespace Emby.Server.Implementations.EntryPoints
{
_createdRules.Clear();
}
-
- lock (_usnsHandledLock)
- {
- _usnsHandled.Clear();
- }
}
- void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
+ private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
- if (_disposed)
- {
- return;
- }
+ NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
+ }
+ private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+ {
try
{
var device = e.Device;
CreateRules(device);
}
- catch
+ catch (Exception ex)
{
- // Commenting out because users are reporting problems out of our control
- //_logger.LogError(ex, "Error creating port forwarding rules");
+ _logger.LogError(ex, "Error creating port forwarding rules");
}
}
@@ -232,15 +152,13 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
- var address = device.LocalAddress;
-
- var addressString = address.ToString();
+ var address = device.DeviceEndpoint;
lock (_createdRulesLock)
{
- if (!_createdRules.Contains(addressString))
+ if (!_createdRules.Contains(address))
{
- _createdRules.Add(addressString);
+ _createdRules.Add(address);
}
else
{
@@ -268,54 +186,43 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- private Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
+ private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
- _logger.LogDebug("Creating port map on local port {0} to public port {1} with device {2}", privatePort, publicPort, device.LocalAddress.ToString());
-
- return device.CreatePortMap(new Mapping(Protocol.Tcp, privatePort, publicPort)
- {
- Description = _appHost.Name
- });
+ _logger.LogDebug(
+ "Creating port map on local port {0} to public port {1} with device {2}",
+ privatePort,
+ publicPort,
+ device.DeviceEndpoint);
+
+ return device.CreatePortMapAsync(
+ new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
}
- private bool _disposed = false;
+ /// <inheritdoc />
public void Dispose()
{
- _disposed = true;
- DisposeNat();
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
- private void DisposeNat()
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
{
- _logger.LogDebug("Stopping NAT discovery");
-
- if (_timer != null)
+ if (_disposed)
{
- _timer.Dispose();
- _timer = null;
+ return;
}
- _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
+ _config.ConfigurationUpdated -= OnConfigurationUpdated;
- var natManager = _natManager;
+ Stop();
- if (natManager != null)
- {
- _natManager = null;
+ _timer = null;
- using (natManager)
- {
- try
- {
- natManager.StopDiscovery();
- natManager.DeviceFound -= NatUtility_DeviceFound;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error stopping NAT Discovery");
- }
- }
- }
+ _disposed = true;
}
}
}
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 9c0db2cf5..7bef2ae58 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.EntryPoints
public class LibraryChangedNotifier : IServerEntryPoint
{
/// <summary>
- /// The _library manager
+ /// The library manager.
/// </summary>
private readonly ILibraryManager _libraryManager;
@@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly ILogger _logger;
/// <summary>
- /// The _library changed sync lock
+ /// The library changed sync lock.
/// </summary>
private readonly object _libraryChangedSyncLock = new object();
@@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.EntryPoints
private Timer LibraryUpdateTimer { get; set; }
/// <summary>
- /// The library update duration
+ /// The library update duration.
/// </summary>
private const int LibraryUpdateDuration = 30000;
@@ -188,8 +188,11 @@ namespace Emby.Server.Implementations.EntryPoints
{
if (LibraryUpdateTimer == null)
{
- LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration,
- Timeout.Infinite);
+ LibraryUpdateTimer = new Timer(
+ LibraryUpdateTimerCallback,
+ null,
+ LibraryUpdateDuration,
+ Timeout.Infinite);
}
else
{
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index e4f98acb9..dc1a56e27 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -7,7 +7,6 @@ using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
using MediaBrowser.Common.Extensions;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
@@ -539,6 +537,11 @@ namespace Emby.Server.Implementations.HttpServer
}
finally
{
+ if (httpRes.StatusCode >= 500)
+ {
+ _logger.LogDebug("Sending HTTP Response 500 in response to {Url}", urlToLog);
+ }
+
stopWatch.Stop();
var elapsed = stopWatch.Elapsed;
if (elapsed.TotalMilliseconds > 500)
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index c95b00ede..85110c21c 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -2,11 +2,11 @@ using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Cryptography;
-using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.Library
{
return string.IsNullOrEmpty(user.EasyPassword)
? null
- : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
+ : Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
}
/// <summary>
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 13857c1e8..528636ecd 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1899,7 +1899,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="cancellationToken">The cancellation token.</param>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
+ UpdateItems(new[] { item }, parent, updateReason, cancellationToken);
}
/// <summary>
@@ -2175,7 +2175,6 @@ namespace Emby.Server.Implementations.Library
DisplayParentId = parentId
};
-
CreateItem(item, null);
isNew = true;
@@ -2197,7 +2196,6 @@ namespace Emby.Server.Implementations.Library
{
// Need to force save to increment DateLastSaved
ForceSave = true
-
},
RefreshPriority.Normal);
}
@@ -2487,6 +2485,15 @@ namespace Emby.Server.Implementations.Library
{
episode.ParentIndexNumber = season.IndexNumber;
}
+ else
+ {
+ /*
+ Anime series don't generally have a season in their file name, however,
+ tvdb needs a season to correctly get the metadata.
+ Hence, a null season needs to be filled with something. */
+ //FIXME perhaps this would be better for tvdb parser to ask for season 1 if no season is specified
+ episode.ParentIndexNumber = 1;
+ }
if (episode.ParentIndexNumber.HasValue)
{
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 2b6ae1297..b4e082b06 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -24,6 +24,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
@@ -31,7 +32,6 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Users;
using Microsoft.Extensions.Logging;
-using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Library
{
@@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.Library
private readonly Func<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
+ private readonly ICryptoProvider _cryptoProvider;
private ConcurrentDictionary<Guid, User> _users;
@@ -80,7 +81,8 @@ namespace Emby.Server.Implementations.Library
Func<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
- IFileSystem fileSystem)
+ IFileSystem fileSystem,
+ ICryptoProvider cryptoProvider)
{
_logger = logger;
_userRepository = userRepository;
@@ -91,6 +93,7 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
+ _cryptoProvider = cryptoProvider;
_users = null;
}
@@ -475,24 +478,21 @@ namespace Emby.Server.Implementations.Library
if (!success
&& _networkManager.IsInLocalNetwork(remoteEndPoint)
- && user.Configuration.EnableLocalPassword)
+ && user.Configuration.EnableLocalPassword
+ && !string.IsNullOrEmpty(user.EasyPassword))
{
- success = string.Equals(
- GetLocalPasswordHash(user),
- _defaultAuthenticationProvider.GetHashedString(user, password),
- StringComparison.OrdinalIgnoreCase);
+ // Check easy password
+ var passwordHash = PasswordHash.Parse(user.EasyPassword);
+ var hash = _cryptoProvider.ComputeHash(
+ passwordHash.Id,
+ Encoding.UTF8.GetBytes(password),
+ passwordHash.Salt);
+ success = passwordHash.Hash.SequenceEqual(hash);
}
return (authenticationProvider, username, success);
}
- private string GetLocalPasswordHash(User user)
- {
- return string.IsNullOrEmpty(user.EasyPassword)
- ? null
- : ToHexString(PasswordHash.Parse(user.EasyPassword).Hash);
- }
-
private void ResetInvalidLoginAttemptCount(User user)
{
user.Policy.InvalidLoginAttemptCount = 0;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 49308b2b1..d4bd598e3 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -2304,8 +2304,10 @@ namespace Emby.Server.Implementations.LiveTv
if (provider == null)
{
throw new ResourceNotFoundException(
- string.Format("Couldn't find provider of type: '{0}'", info.Type)
- );
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Couldn't find provider of type: '{0}'",
+ info.Type));
}
await provider.Validate(info, validateLogin, validateListings).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index d12c96392..2ea171031 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -68,7 +68,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected void SetTempFilePath(string extension)
{
- TempFilePath = Path.Combine(AppPaths.GetTranscodingTempPath(), UniqueId + "." + extension);
+ TempFilePath = Path.Combine(AppPaths.GetTranscodePath(), UniqueId + "." + extension);
}
public virtual Task Open(CancellationToken openCancellationToken)
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index a71dc9346..bfe32a6c2 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -1,22 +1,22 @@
{
"Albums": "Албуми",
- "AppDeviceValues": "Програма: {0}, Устройство: {1}",
+ "AppDeviceValues": "Програма: {0}, устройство: {1}",
"Application": "Програма",
"Artists": "Изпълнители",
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
"Books": "Книги",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+ "CameraImageUploadedFrom": "",
"Channels": "Канали",
"ChapterNameValue": "Глава {0}",
"Collections": "Колекции",
"DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
+ "FailedLoginAttemptWithUserName": "",
"Favorites": "Любими",
"Folders": "Папки",
"Genres": "Жанрове",
"HeaderAlbumArtists": "Изпълнители на албуми",
- "HeaderCameraUploads": "Camera Uploads",
+ "HeaderCameraUploads": "",
"HeaderContinueWatching": "Продължаване на гледането",
"HeaderFavoriteAlbums": "Любими албуми",
"HeaderFavoriteArtists": "Любими изпълнители",
@@ -25,26 +25,26 @@
"HeaderFavoriteSongs": "Любими песни",
"HeaderLiveTV": "Телевизия на живо",
"HeaderNextUp": "Следва",
- "HeaderRecordingGroups": "Recording Groups",
+ "HeaderRecordingGroups": "",
"HomeVideos": "Домашни клипове",
"Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
"LabelIpAddressValue": "ИП адрес: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
+ "LabelRunningTimeValue": "",
"Latest": "Последни",
"MessageApplicationUpdated": "Сървърът е обновен",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
+ "MessageApplicationUpdatedTo": "",
+ "MessageNamedServerConfigurationUpdatedWithValue": "",
+ "MessageServerConfigurationUpdated": "",
"MixedContent": "Смесено съдържание",
"Movies": "Филми",
"Music": "Музика",
"MusicVideos": "Музикални клипове",
- "NameInstallFailed": "{0} installation failed",
+ "NameInstallFailed": "",
"NameSeasonNumber": "Сезон {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
+ "NameSeasonUnknown": "Неразпознат сезон",
+ "NewVersionIsAvailable": "",
"NotificationOptionApplicationUpdateAvailable": "Налично е обновление на програмата",
"NotificationOptionApplicationUpdateInstalled": "Обновлението на програмата е инсталирано",
"NotificationOptionAudioPlayback": "Възпроизвеждането на звук започна",
@@ -58,7 +58,7 @@
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
"NotificationOptionTaskFailed": "Грешка в планирана задача",
- "NotificationOptionUserLockedOut": "User locked out",
+ "NotificationOptionUserLockedOut": "",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки",
@@ -70,12 +70,12 @@
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
+ "ServerNameNeedsToBeRestarted": "",
"Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
- "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
+ "SubtitleDownloadFailureFromForItem": "",
"SubtitlesDownloadedForItem": "Изтеглени са субтитри за {0}",
"Sync": "Синхронизиране",
"System": "Система",
@@ -83,15 +83,15 @@
"User": "Потребител",
"UserCreatedWithName": "Потребителят {0} е създаден",
"UserDeletedWithName": "Потребителят {0} е изтрит",
- "UserDownloadingItemWithValues": "{0} is downloading {1}",
- "UserLockedOutWithName": "User {0} has been locked out",
+ "UserDownloadingItemWithValues": "",
+ "UserLockedOutWithName": "",
"UserOfflineFromDevice": "{0} се разкачи от {1}",
"UserOnlineFromDevice": "{0} е на линия от {1}",
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
- "UserPolicyUpdatedWithName": "User policy has been updated for {0}",
+ "UserPolicyUpdatedWithName": "",
"UserStartedPlayingItemWithValues": "{0} пусна {1}",
"UserStoppedPlayingItemWithValues": "{0} спря {1}",
- "ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
+ "ValueHasBeenAddedToLibrary": "",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index c19148921..86fbac380 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -23,7 +23,7 @@
"HeaderFavoriteEpisodes": "Oblíbené epizody",
"HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba",
- "HeaderLiveTV": "Live TV",
+ "HeaderLiveTV": "Živá TV",
"HeaderNextUp": "Nadcházející",
"HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domáci videa",
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index a2fc126a2..3a6852321 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -5,7 +5,7 @@
"Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen azonosítva",
"Books": "Könyvek",
- "CameraImageUploadedFrom": "Új kamerakép került feltöltésre {0}",
+ "CameraImageUploadedFrom": "Új kamerakép került feltöltésre innen: {0}",
"Channels": "Csatornák",
"ChapterNameValue": "Jelenet {0}",
"Collections": "Gyűjtemények",
@@ -15,14 +15,14 @@
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
- "HeaderAlbumArtists": "Album Előadók",
+ "HeaderAlbumArtists": "Album előadók",
"HeaderCameraUploads": "Kamera feltöltések",
"HeaderContinueWatching": "Folyamatban lévő filmek",
- "HeaderFavoriteAlbums": "Kedvenc Albumok",
- "HeaderFavoriteArtists": "Kedvenc Előadók",
- "HeaderFavoriteEpisodes": "Kedvenc Epizódok",
- "HeaderFavoriteShows": "Kedvenc Sorozatok",
- "HeaderFavoriteSongs": "Kedvenc Dalok",
+ "HeaderFavoriteAlbums": "Kedvenc albumok",
+ "HeaderFavoriteArtists": "Kedvenc előadók",
+ "HeaderFavoriteEpisodes": "Kedvenc epizódok",
+ "HeaderFavoriteShows": "Kedvenc sorozatok",
+ "HeaderFavoriteSongs": "Kedvenc dalok",
"HeaderLiveTV": "Élő TV",
"HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Felvételi csoportok",
@@ -34,21 +34,21 @@
"LabelRunningTimeValue": "Futási idő: {0}",
"Latest": "Legújabb",
"MessageApplicationUpdated": "Jellyfin Szerver frissítve",
- "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész {0} frissítve",
+ "MessageApplicationUpdatedTo": "Jellyfin Szerver frissítve lett a következőre: {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Szerver konfigurációs rész frissítve: {0}",
"MessageServerConfigurationUpdated": "Szerver konfiguráció frissítve",
"MixedContent": "Vegyes tartalom",
"Movies": "Filmek",
"Music": "Zene",
- "MusicVideos": "Zenei Videók",
+ "MusicVideos": "Zenei videók",
"NameInstallFailed": "{0} sikertelen telepítés",
"NameSeasonNumber": "Évad {0}",
"NameSeasonUnknown": "Ismeretlen évad",
"NewVersionIsAvailable": "Letölthető a Jellyfin Szerver új verziója.",
- "NotificationOptionApplicationUpdateAvailable": "Új programfrissítés érhető el",
- "NotificationOptionApplicationUpdateInstalled": "Programfrissítés telepítve",
+ "NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
+ "NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
"NotificationOptionAudioPlayback": "Audió lejátszás elkezdve",
- "NotificationOptionAudioPlaybackStopped": "Audió lejátszás befejezve",
+ "NotificationOptionAudioPlaybackStopped": "Audió lejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamera kép feltöltve",
"NotificationOptionInstallationFailed": "Telepítési hiba",
"NotificationOptionNewLibraryContent": "Új tartalom hozzáadva",
@@ -60,15 +60,15 @@
"NotificationOptionTaskFailed": "Ütemezett feladat hiba",
"NotificationOptionUserLockedOut": "Felhasználó tiltva",
"NotificationOptionVideoPlayback": "Videó lejátszás elkezdve",
- "NotificationOptionVideoPlaybackStopped": "Videó lejátszás befejezve",
+ "NotificationOptionVideoPlaybackStopped": "Videó lejátszás leállítva",
"Photos": "Fényképek",
"Playlists": "Lejátszási listák",
"Plugin": "Bővítmény",
"PluginInstalledWithName": "{0} telepítve",
"PluginUninstalledWithName": "{0} eltávolítva",
"PluginUpdatedWithName": "{0} frissítve",
- "ProviderValue": "Provider: {0}",
- "ScheduledTaskFailedWithName": "{0} hiba",
+ "ProviderValue": "Szolgáltató: {0}",
+ "ScheduledTaskFailedWithName": "{0} sikertelen",
"ScheduledTaskStartedWithName": "{0} elkezdve",
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
"Shows": "Műsorok",
@@ -76,10 +76,10 @@
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek próbáld újra később.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Nem sikerült a felirat letöltése innen: {0} ehhez: {1}",
- "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz {0}",
+ "SubtitlesDownloadedForItem": "Letöltött feliratok a következőhöz: {0}",
"Sync": "Szinkronizál",
"System": "Rendszer",
- "TvShows": "TV Műsorok",
+ "TvShows": "TV műsorok",
"User": "Felhasználó",
"UserCreatedWithName": "{0} felhasználó létrehozva",
"UserDeletedWithName": "{0} felhasználó törölve",
@@ -88,7 +88,7 @@
"UserOfflineFromDevice": "{0} kijelentkezett innen: {1}",
"UserOnlineFromDevice": "{0} online itt: {1}",
"UserPasswordChangedWithName": "Jelszó megváltozott a következő felhasználó számára: {0}",
- "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett {0}",
+ "UserPolicyUpdatedWithName": "A felhasználói házirend frissítve lett neki: {0}",
"UserStartedPlayingItemWithValues": "{0} elkezdte játszani a következőt: {1} itt: {2}",
"UserStoppedPlayingItemWithValues": "{0} befejezte a következőt: {1} itt: {2}",
"ValueHasBeenAddedToLibrary": "{0} hozzáadva a médiatárhoz",
diff --git a/Emby.Server.Implementations/Localization/Core/nl.json b/Emby.Server.Implementations/Localization/Core/nl.json
index 24af1839f..637e514ed 100644
--- a/Emby.Server.Implementations/Localization/Core/nl.json
+++ b/Emby.Server.Implementations/Localization/Core/nl.json
@@ -1,7 +1,7 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparaat: {1}",
- "Application": "Toepassing",
+ "Application": "Applicatie",
"Artists": "Artiesten",
"AuthenticationSucceededWithUserName": "{0} is succesvol geverifieerd",
"Books": "Boeken",
@@ -30,7 +30,7 @@
"Inherit": "Overerven",
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
- "LabelIpAddressValue": "IP adres: {0}",
+ "LabelIpAddressValue": "IP-adres: {0}",
"LabelRunningTimeValue": "Looptijd: {0}",
"Latest": "Nieuwste",
"MessageApplicationUpdated": "Jellyfin Server is bijgewerkt",
@@ -50,7 +50,7 @@
"NotificationOptionAudioPlayback": "Muziek gestart",
"NotificationOptionAudioPlaybackStopped": "Muziek gestopt",
"NotificationOptionCameraImageUploaded": "Camera-afbeelding geüpload",
- "NotificationOptionInstallationFailed": "Installatie mislukt",
+ "NotificationOptionInstallationFailed": "Installatie mislukking",
"NotificationOptionNewLibraryContent": "Nieuwe content toegevoegd",
"NotificationOptionPluginError": "Plug-in fout",
"NotificationOptionPluginInstalled": "Plug-in geïnstalleerd",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index ba5e93982..87f8553ae 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -5,7 +5,7 @@
"Artists": "艺术家",
"AuthenticationSucceededWithUserName": "{0} 认证成功",
"Books": "书籍",
- "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相机图像",
+ "CameraImageUploadedFrom": "已从 {0} 上传了一张新的相片",
"Channels": "频道",
"ChapterNameValue": "章节 {0}",
"Collections": "合集",
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
index c343a7d48..200649ba9 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
try
{
- DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodingTempPath, minDateModified, progress);
+ DeleteTempFilesFromDirectory(cancellationToken, ApplicationPaths.TranscodePath, minDateModified, progress);
}
catch (DirectoryNotFoundException)
{
@@ -138,13 +138,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
}
}
- public string Name => "Transcoding temp cleanup";
+ public string Name => "Transcode file cleanup";
- public string Description => "Deletes transcoding temp files older than 24 hours.";
+ public string Description => "Deletes transcode files more than 24 hours old.";
public string Category => "Maintenance";
- public string Key => "DeleteTranscodingTempFiles";
+ public string Key => "DeleteTranscodeFiles";
public bool IsHidden => false;
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index 7afeba9dd..fe8deae59 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -1,24 +1,23 @@
-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.Common.Updates;
+using MediaBrowser.Model.Net;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.ScheduledTasks
{
/// <summary>
- /// Plugin Update Task
+ /// Plugin Update Task.
/// </summary>
public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask
{
/// <summary>
- /// The _logger
+ /// The _logger.
/// </summary>
private readonly ILogger _logger;
@@ -31,7 +30,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Creates the triggers that define when the task will run
+ /// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
@@ -44,16 +43,16 @@ namespace Emby.Server.Implementations.ScheduledTasks
}
/// <summary>
- /// Update installed plugins
+ /// Update installed plugins.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
- /// <returns>Task.</returns>
+ /// <returns><see cref="Task" />.</returns>
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
progress.Report(0);
- var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList();
+ var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(cancellationToken).ConfigureAwait(false)).ToList();
progress.Report(10);
@@ -94,18 +93,25 @@ namespace Emby.Server.Implementations.ScheduledTasks
progress.Report(100);
}
+ /// <inheritdoc />
public string Name => "Check for plugin updates";
+ /// <inheritdoc />
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
+ /// <inheritdoc />
public string Category => "Application";
+ /// <inheritdoc />
public string Key => "PluginUpdates";
+ /// <inheritdoc />
public bool IsHidden => false;
+ /// <inheritdoc />
public bool IsEnabled => true;
+ /// <inheritdoc />
public bool IsLogged => true;
}
}
diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs
index 46195cc74..87de9804a 100644
--- a/Emby.Server.Implementations/ServerApplicationPaths.cs
+++ b/Emby.Server.Implementations/ServerApplicationPaths.cs
@@ -10,8 +10,8 @@ namespace Emby.Server.Implementations
/// </summary>
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
{
- private string _defaultTranscodingTempPath;
- private string _transcodingTempPath;
+ private string _defaultTranscodePath;
+ private string _transcodePath;
private string _internalMetadataPath;
/// <summary>
@@ -107,19 +107,19 @@ namespace Emby.Server.Implementations
/// <value>The user configuration directory path.</value>
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
- public string DefaultTranscodingTempPath => _defaultTranscodingTempPath ?? (_defaultTranscodingTempPath = Path.Combine(ProgramDataPath, "transcoding-temp"));
+ public string DefaultTranscodePath => _defaultTranscodePath ?? (_defaultTranscodePath = Path.Combine(ProgramDataPath, "transcodes"));
- public string TranscodingTempPath
+ public string TranscodePath
{
- get => _transcodingTempPath ?? (_transcodingTempPath = DefaultTranscodingTempPath);
- set => _transcodingTempPath = value;
+ get => _transcodePath ?? (_transcodePath = DefaultTranscodePath);
+ set => _transcodePath = value;
}
- public string GetTranscodingTempPath()
+ public string GetTranscodePath()
{
- var path = TranscodingTempPath;
+ var path = TranscodePath;
- if (!string.Equals(path, DefaultTranscodingTempPath, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(path, DefaultTranscodePath, StringComparison.OrdinalIgnoreCase))
{
try
{
@@ -136,7 +136,7 @@ namespace Emby.Server.Implementations
}
}
- path = DefaultTranscodingTempPath;
+ path = DefaultTranscodePath;
Directory.CreateDirectory(path);
return path;
}
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index a1e2f9079..1c5402268 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -19,12 +19,11 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Updates;
using Microsoft.Extensions.Logging;
-using static MediaBrowser.Common.HexHelper;
namespace Emby.Server.Implementations.Updates
{
/// <summary>
- /// Manages all install, uninstall and update operations (both plugins and system)
+ /// Manages all install, uninstall and update operations (both plugins and system).
/// </summary>
public class InstallationManager : IInstallationManager
{
@@ -51,12 +50,12 @@ namespace Emby.Server.Implementations.Updates
/// <summary>
/// The current installations.
/// </summary>
- private List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations;
+ private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations;
/// <summary>
/// The completed installations.
/// </summary>
- private ConcurrentBag<InstallationInfo> _completedInstallationsInternal;
+ private readonly ConcurrentBag<InstallationInfo> _completedInstallationsInternal;
public InstallationManager(
ILogger<InstallationManager> logger,
@@ -86,51 +85,32 @@ namespace Emby.Server.Implementations.Updates
_zipClient = zipClient;
}
+ /// <inheritdoc />
public event EventHandler<InstallationEventArgs> PackageInstalling;
+ /// <inheritdoc />
public event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
+ /// <inheritdoc />
public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
+ /// <inheritdoc />
public event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
- /// <summary>
- /// Occurs when a plugin is uninstalled.
- /// </summary>
+ /// <inheritdoc />
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
- /// <summary>
- /// Occurs when a plugin plugin is updated.
- /// </summary>
+ /// <inheritdoc />
public event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
- /// <summary>
- /// Occurs when a plugin plugin is installed.
- /// </summary>
+ /// <inheritdoc />
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
+ /// <inheritdoc />
public IEnumerable<InstallationInfo> CompletedInstallations => _completedInstallationsInternal;
- /// <summary>
- /// Gets all available packages.
- /// </summary>
- /// <returns>Task{List{PackageInfo}}.</returns>
- public async Task<List<PackageInfo>> GetAvailablePackages(
- CancellationToken cancellationToken,
- bool withRegistration = true,
- string packageType = null,
- Version applicationVersion = null)
- {
- var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
- return FilterPackages(packages, packageType, applicationVersion);
- }
-
- /// <summary>
- /// Gets all available packages.
- /// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{List{PackageInfo}}.</returns>
- public async Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
{
using (var response = await _httpClient.SendAsync(
new HttpRequestOptions
@@ -138,178 +118,91 @@ namespace Emby.Server.Implementations.Updates
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
CancellationToken = cancellationToken,
CacheMode = CacheMode.Unconditional,
- CacheLength = GetCacheLength()
+ CacheLength = TimeSpan.FromMinutes(3)
},
HttpMethod.Get).ConfigureAwait(false))
using (Stream stream = response.Content)
{
- return FilterPackages(await _jsonSerializer.DeserializeFromStreamAsync<PackageInfo[]>(stream).ConfigureAwait(false));
+ return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
+ stream).ConfigureAwait(false);
}
}
- private static TimeSpan GetCacheLength()
- {
- return TimeSpan.FromMinutes(3);
- }
-
- protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages)
+ /// <inheritdoc />
+ public IEnumerable<PackageInfo> FilterPackages(
+ IEnumerable<PackageInfo> availablePackages,
+ string name = null,
+ Guid guid = default)
{
- var list = new List<PackageInfo>();
-
- foreach (var package in packages)
+ if (name != null)
{
- var versions = new List<PackageVersionInfo>();
- foreach (var version in package.versions)
- {
- if (string.IsNullOrEmpty(version.sourceUrl))
- {
- continue;
- }
-
- versions.Add(version);
- }
-
- package.versions = versions
- .OrderByDescending(x => x.Version)
- .ToArray();
-
- if (package.versions.Length == 0)
- {
- continue;
- }
-
- list.Add(package);
+ availablePackages = availablePackages.Where(x => x.name.Equals(name, StringComparison.OrdinalIgnoreCase));
}
- // Remove packages with no versions
- return list;
- }
-
- protected List<PackageInfo> FilterPackages(IEnumerable<PackageInfo> packages, string packageType, Version applicationVersion)
- {
- var packagesList = FilterPackages(packages);
-
- var returnList = new List<PackageInfo>();
-
- var filterOnPackageType = !string.IsNullOrEmpty(packageType);
-
- foreach (var p in packagesList)
+ if (guid != Guid.Empty)
{
- if (filterOnPackageType && !string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- // If an app version was supplied, filter the versions for each package to only include supported versions
- if (applicationVersion != null)
- {
- p.versions = p.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToArray();
- }
-
- if (p.versions.Length == 0)
- {
- continue;
- }
-
- returnList.Add(p);
+ var strGuid = guid.ToString("N", CultureInfo.InvariantCulture);
+ availablePackages = availablePackages.Where(x => x.guid.Equals(strGuid, StringComparison.OrdinalIgnoreCase));
}
- return returnList;
+ return availablePackages;
}
- /// <summary>
- /// Determines whether [is package version up to date] [the specified package version info].
- /// </summary>
- /// <param name="packageVersionInfo">The package version info.</param>
- /// <param name="currentServerVersion">The current server version.</param>
- /// <returns><c>true</c> if [is package version up to date] [the specified package version info]; otherwise, <c>false</c>.</returns>
- private static bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version currentServerVersion)
+ /// <inheritdoc />
+ public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ IEnumerable<PackageVersionInfo> availableVersions,
+ Version minVersion = null,
+ PackageVersionClass classification = PackageVersionClass.Release)
{
- if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr))
+ var appVer = _applicationHost.ApplicationVersion;
+ availableVersions = availableVersions
+ .Where(x => x.classification == classification
+ && Version.Parse(x.requiredVersionStr) <= appVer);
+
+ if (minVersion != null)
{
- return true;
+ availableVersions = availableVersions.Where(x => x.Version >= minVersion);
}
- return Version.TryParse(packageVersionInfo.requiredVersionStr, out var requiredVersion) && currentServerVersion >= requiredVersion;
+ return availableVersions.OrderByDescending(x => x.Version);
}
- /// <summary>
- /// Gets the package.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="guid">The assembly guid</param>
- /// <param name="classification">The classification.</param>
- /// <param name="version">The version.</param>
- /// <returns>Task{PackageVersionInfo}.</returns>
- public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version)
+ /// <inheritdoc />
+ public IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ IEnumerable<PackageInfo> availablePackages,
+ string name = null,
+ Guid guid = default,
+ Version minVersion = null,
+ PackageVersionClass classification = PackageVersionClass.Release)
{
- var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
-
- var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
- ?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
+ var package = FilterPackages(availablePackages, name, guid).FirstOrDefault();
+ // Package not found.
if (package == null)
{
return null;
}
- return package.versions.FirstOrDefault(v => v.Version == version && v.classification == classification);
- }
-
- /// <summary>
- /// Gets the latest compatible version.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="guid">The assembly guid if this is a plug-in</param>
- /// <param name="currentServerVersion">The current server version.</param>
- /// <param name="classification">The classification.</param>
- /// <returns>Task{PackageVersionInfo}.</returns>
- public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
- {
- var packages = await GetAvailablePackages(CancellationToken.None, false).ConfigureAwait(false);
-
- return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification);
- }
-
- /// <summary>
- /// Gets the latest compatible version.
- /// </summary>
- /// <param name="availablePackages">The available packages.</param>
- /// <param name="name">The name.</param>
- /// <param name="currentServerVersion">The current server version.</param>
- /// <param name="classification">The classification.</param>
- /// <returns>PackageVersionInfo.</returns>
- public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
- {
- var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
- ?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
-
- return package?.versions
- .OrderByDescending(x => x.Version)
- .FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
+ return GetCompatibleVersions(
+ package.versions,
+ minVersion,
+ classification);
}
- /// <summary>
- /// Gets the available plugin updates.
- /// </summary>
- /// <param name="applicationVersion">The current server version.</param>
- /// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
- public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
{
- var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
+ var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
// Figure out what needs to be installed
- return _applicationHost.Plugins.Select(p =>
+ return _applicationHost.Plugins.Select(x =>
{
- var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, systemUpdateLevel);
-
- return latestPluginInfo != null && latestPluginInfo.Version > p.Version ? latestPluginInfo : null;
- }).Where(i => i != null)
- .Where(p => !string.IsNullOrEmpty(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
+ var compatibleversions = GetCompatibleVersions(catalog, x.Name, x.Id, x.Version, systemUpdateLevel);
+ return compatibleversions.FirstOrDefault(y => y.Version > x.Version);
+ }).Where(x => x != null)
+ .Where(x => !CompletedInstallations.Any(y => string.Equals(y.AssemblyGuid, x.guid, StringComparison.OrdinalIgnoreCase)));
}
/// <inheritdoc />
@@ -395,7 +288,7 @@ namespace Emby.Server.Implementations.Updates
finally
{
// Dispose the progress object and remove the installation from the in-progress list
- tuple.Item2.Dispose();
+ tuple.innerCancellationTokenSource.Dispose();
}
}
@@ -443,7 +336,7 @@ namespace Emby.Server.Implementations.Updates
// Always override the passed-in target (which is a file) and figure it out again
string targetDir = Path.Combine(_appPaths.PluginsPath, package.name);
-// CA5351: Do Not Use Broken Cryptographic Algorithms
+ // CA5351: Do Not Use Broken Cryptographic Algorithms
#pragma warning disable CA5351
using (var res = await _httpClient.SendAsync(
new HttpRequestOptions
@@ -459,7 +352,7 @@ namespace Emby.Server.Implementations.Updates
{
cancellationToken.ThrowIfCancellationRequested();
- var hash = ToHexString(md5.ComputeHash(stream));
+ var hash = Hex.Encode(md5.ComputeHash(stream));
if (!string.Equals(package.checksum, hash, StringComparison.OrdinalIgnoreCase))
{
_logger.LogError(
@@ -541,18 +434,19 @@ namespace Emby.Server.Implementations.Updates
{
lock (_currentInstallationsLock)
{
- var install = _currentInstallations.Find(x => x.Item1.Id == id);
+ var install = _currentInstallations.Find(x => x.info.Id == id);
if (install == default((InstallationInfo, CancellationTokenSource)))
{
return false;
}
- install.Item2.Cancel();
+ install.token.Cancel();
_currentInstallations.Remove(install);
return true;
}
}
+ /// <inheritdoc />
public void Dispose()
{
Dispose(true);
@@ -571,7 +465,7 @@ namespace Emby.Server.Implementations.Updates
{
foreach (var tuple in _currentInstallations)
{
- tuple.Item2.Dispose();
+ tuple.token.Dispose();
}
_currentInstallations.Clear();
diff --git a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 396bdd4b7..988ac364a 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 7dca7e814..a962b0240 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -40,14 +40,13 @@ namespace MediaBrowser.Api
internal IHttpResultFactory ResultFactory { get; private set; }
/// <summary>
- /// The application paths
+ /// Gets the configuration manager.
/// </summary>
- private readonly IServerConfigurationManager _config;
+ internal IServerConfigurationManager ConfigurationManager { get; }
private readonly ISessionManager _sessionManager;
private readonly IFileSystem _fileSystem;
private readonly IMediaSourceManager _mediaSourceManager;
- public readonly IProcessFactory ProcessFactory;
/// <summary>
/// The active transcoding jobs
@@ -73,15 +72,13 @@ namespace MediaBrowser.Api
IServerConfigurationManager config,
IFileSystem fileSystem,
IMediaSourceManager mediaSourceManager,
- IProcessFactory processFactory,
IHttpResultFactory resultFactory)
{
Logger = logger;
_sessionManager = sessionManager;
- _config = config;
+ ConfigurationManager = config;
_fileSystem = fileSystem;
_mediaSourceManager = mediaSourceManager;
- ProcessFactory = processFactory;
ResultFactory = resultFactory;
_sessionManager.PlaybackProgress += _sessionManager_PlaybackProgress;
@@ -162,7 +159,7 @@ namespace MediaBrowser.Api
public EncodingOptions GetEncodingOptions()
{
- return ConfigurationManagerExtensions.GetConfiguration<EncodingOptions>(_config, "encoding");
+ return ConfigurationManagerExtensions.GetConfiguration<EncodingOptions>(ConfigurationManager, "encoding");
}
/// <summary>
@@ -170,7 +167,7 @@ namespace MediaBrowser.Api
/// </summary>
private void DeleteEncodedMediaCache()
{
- var path = _config.ApplicationPaths.GetTranscodingTempPath();
+ var path = ConfigurationManager.ApplicationPaths.GetTranscodePath();
if (!Directory.Exists(path))
{
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs
index 49f8c6ace..311105596 100644
--- a/MediaBrowser.Api/BaseApiService.cs
+++ b/MediaBrowser.Api/BaseApiService.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -298,11 +297,24 @@ namespace MediaBrowser.Api
var pathInfo = Parse(Request.PathInfo);
var first = pathInfo[0];
+ string baseUrl = ApiEntryPoint.Instance.ConfigurationManager.Configuration.BaseUrl;
+
// backwards compatibility
- if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
+ if (baseUrl.Length == 0
+ && (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase)))
+ {
+ index++;
+ }
+ else if (string.Equals(first, baseUrl.Remove(0, 1)))
{
index++;
+ var second = pathInfo[1];
+ if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
+ {
+ index++;
+ }
}
return pathInfo[index];
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index b05e8c949..2b9a64e97 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Api.UserLibrary;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
@@ -25,7 +26,6 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Services;
using Microsoft.Net.Http.Headers;
-using static MediaBrowser.Common.HexHelper;
namespace MediaBrowser.Api.LiveTv
{
@@ -887,8 +887,9 @@ namespace MediaBrowser.Api.LiveTv
{
// SchedulesDirect requires a SHA1 hash of the user's password
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
- using (SHA1 sha = SHA1.Create()) {
- return ToHexString(
+ using (SHA1 sha = SHA1.Create())
+ {
+ return Hex.Encode(
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
}
}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index f653270a6..0d62cf8c5 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -10,7 +10,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index baa6f7bb9..1e5a93210 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Progress;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
@@ -131,15 +131,9 @@ namespace MediaBrowser.Api
/// </summary>
/// <param name="request">The request.</param>
/// <returns>System.Object.</returns>
- ///
- /// <summary>
- /// Gets the specified request.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <returns>System.Object.</returns>
public object Get(GetPackage request)
{
- var packages = _installationManager.GetAvailablePackages(CancellationToken.None, applicationVersion: typeof(PackageService).Assembly.GetName().Version).Result;
+ var packages = _installationManager.GetAvailablePackages().Result;
var result = packages.FirstOrDefault(p => string.Equals(p.guid, request.AssemblyGuid ?? "none", StringComparison.OrdinalIgnoreCase))
?? packages.FirstOrDefault(p => p.name.Equals(request.Name, StringComparison.OrdinalIgnoreCase));
@@ -154,7 +148,7 @@ namespace MediaBrowser.Api
/// <returns>System.Object.</returns>
public async Task<object> Get(GetPackages request)
{
- IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, typeof(PackageService).Assembly.GetName().Version).ConfigureAwait(false);
+ IEnumerable<PackageInfo> packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
if (!string.IsNullOrEmpty(request.TargetSystems))
{
@@ -163,11 +157,6 @@ namespace MediaBrowser.Api
packages = packages.Where(p => apps.Contains(p.targetSystem));
}
- if (request.IsPremium.HasValue)
- {
- packages = packages.Where(p => p.isPremium == request.IsPremium.Value);
- }
-
if (request.IsAdult.HasValue)
{
packages = packages.Where(p => p.adult == request.IsAdult.Value);
@@ -188,13 +177,21 @@ namespace MediaBrowser.Api
/// <exception cref="ResourceNotFoundException"></exception>
public async Task Post(InstallPackage request)
{
- var package = string.IsNullOrEmpty(request.Version) ?
- await _installationManager.GetLatestCompatibleVersion(request.Name, request.AssemblyGuid, typeof(PackageService).Assembly.GetName().Version, request.UpdateClass).ConfigureAwait(false) :
- await _installationManager.GetPackage(request.Name, request.AssemblyGuid, request.UpdateClass, Version.Parse(request.Version)).ConfigureAwait(false);
+ var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
+ var package = _installationManager.GetCompatibleVersions(
+ packages,
+ request.Name,
+ new Guid(request.AssemblyGuid),
+ string.IsNullOrEmpty(request.Version) ? null : Version.Parse(request.Version),
+ request.UpdateClass).FirstOrDefault();
if (package == null)
{
- throw new ResourceNotFoundException(string.Format("Package not found: {0}", request.Name));
+ throw new ResourceNotFoundException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Package not found: {0}",
+ request.Name));
}
await _installationManager.InstallPackage(package);
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 8c4ccfa22..c7104c950 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -141,7 +141,7 @@ namespace MediaBrowser.Api.Playback
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
var ext = outputFileExtension.ToLowerInvariant();
- var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
+ var folder = ServerConfigurationManager.ApplicationPaths.TranscodePath;
if (EnableOutputInSubFolder)
{
@@ -289,17 +289,22 @@ namespace MediaBrowser.Api.Playback
throw;
}
+ Logger.LogDebug("Launched ffmpeg process");
state.TranscodingJob = transcodingJob;
// 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(state, process.StandardError.BaseStream, logStream);
// Wait for the file to exist before proceeeding
- while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
+ var ffmpegTargetFile = state.WaitForPath ?? outputPath;
+ Logger.LogDebug("Waiting for the creation of {0}", ffmpegTargetFile);
+ while (!File.Exists(ffmpegTargetFile) && !transcodingJob.HasExited)
{
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
+ Logger.LogDebug("File {0} created or transcoding has finished", ffmpegTargetFile);
+
if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
{
await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
@@ -314,6 +319,7 @@ namespace MediaBrowser.Api.Playback
{
StartThrottler(state, transcodingJob);
}
+ Logger.LogDebug("StartFfMpeg() finished successfully");
return transcodingJob;
}
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index f5f753684..9ecb5fe8c 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -192,6 +192,7 @@ namespace MediaBrowser.Api.Playback.Hls
if (File.Exists(segmentPath))
{
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+ Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -207,6 +208,7 @@ namespace MediaBrowser.Api.Playback.Hls
job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
transcodingLock.Release();
released = true;
+ Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
else
@@ -243,6 +245,7 @@ namespace MediaBrowser.Api.Playback.Hls
request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex);
+ state.WaitForPath = segmentPath;
job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
}
catch
@@ -277,7 +280,7 @@ namespace MediaBrowser.Api.Playback.Hls
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
//}
- Logger.LogInformation("returning {0}", segmentPath);
+ Logger.LogDebug("returning {0} [general case]", segmentPath);
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
}
@@ -458,56 +461,68 @@ namespace MediaBrowser.Api.Playback.Hls
TranscodingJob transcodingJob,
CancellationToken cancellationToken)
{
- var segmentFileExists = File.Exists(segmentPath);
-
- // If all transcoding has completed, just return immediately
- if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists)
+ var segmentExists = File.Exists(segmentPath);
+ if (segmentExists)
{
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
- }
+ if (transcodingJob != null && transcodingJob.HasExited)
+ {
+ // Transcoding job is over, so assume all existing files are ready
+ Logger.LogDebug("serving up {0} as transcode is over", segmentPath);
+ return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
+ }
- if (segmentFileExists)
- {
var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
// If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
if (segmentIndex < currentTranscodingIndex)
{
+ Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex);
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
}
}
- var segmentFilename = Path.GetFileName(segmentPath);
-
- while (!cancellationToken.IsCancellationRequested)
+ var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1);
+ if (transcodingJob != null)
{
- try
+ while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited)
{
- var text = File.ReadAllText(playlistPath, Encoding.UTF8);
-
- // If it appears in the playlist, it's done
- if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
+ // To be considered ready, the segment file has to exist AND
+ // either the transcoding job should be done or next segment should also exist
+ if (segmentExists)
{
- if (!segmentFileExists)
+ if (transcodingJob.HasExited || File.Exists(nextSegmentPath))
{
- segmentFileExists = File.Exists(segmentPath);
+ Logger.LogDebug("serving up {0} as it deemed ready", segmentPath);
+ return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
}
- if (segmentFileExists)
+ }
+ else
+ {
+ segmentExists = File.Exists(segmentPath);
+ if (segmentExists)
{
- return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
+ continue; // avoid unnecessary waiting if segment just became available
}
- //break;
}
+
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (!File.Exists(segmentPath))
+ {
+ Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath);
}
- catch (IOException)
+ else
{
- // May get an error if the file is locked
+ Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath);
}
-
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
+ }
+ else
+ {
+ Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath);
}
- cancellationToken.ThrowIfCancellationRequested();
return await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false);
}
@@ -521,6 +536,7 @@ namespace MediaBrowser.Api.Playback.Hls
FileShare = FileShareMode.ReadWrite,
OnComplete = () =>
{
+ Logger.LogDebug("finished serving {0}", segmentPath);
if (transcodingJob != null)
{
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
@@ -909,9 +925,23 @@ namespace MediaBrowser.Api.Playback.Hls
else
{
var keyFrameArg = string.Format(
+ CultureInfo.InvariantCulture,
" -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
GetStartNumber(state) * state.SegmentLength,
- state.SegmentLength.ToString(CultureInfo.InvariantCulture));
+ state.SegmentLength);
+ if (state.TargetFramerate.HasValue)
+ {
+ // This is to make sure keyframe interval is limited to our segment,
+ // as forcing keyframes is not enough.
+ // Example: we encoded half of desired length, then codec detected
+ // scene cut and inserted a keyframe; next forced keyframe would
+ // be created outside of segment, which breaks seeking.
+ keyFrameArg += string.Format(
+ CultureInfo.InvariantCulture,
+ " -g {0} -keyint_min {0}",
+ (int)(state.SegmentLength * state.TargetFramerate)
+ );
+ }
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -955,6 +985,15 @@ namespace MediaBrowser.Api.Playback.Hls
var threads = EncodingHelper.GetNumberOfThreads(state, encodingOptions, videoCodec);
+ if (state.BaseRequest.BreakOnNonKeyFrames)
+ {
+ // FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
+ // breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
+ // to produce a missing part of video stream before first keyframe is encountered, which may lead to
+ // awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
+ Logger.LogInformation("Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request");
+ state.BaseRequest.BreakOnNonKeyFrames = false;
+ }
var inputModifier = EncodingHelper.GetInputModifier(state, encodingOptions);
// If isEncoding is true we're actually starting ffmpeg
@@ -965,14 +1004,6 @@ namespace MediaBrowser.Api.Playback.Hls
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request);
- var timeDeltaParam = string.Empty;
-
- if (isEncoding && state.TargetFramerate > 0)
- {
- float startTime = 1 / (state.TargetFramerate.Value * 2);
- timeDeltaParam = string.Format(CultureInfo.InvariantCulture, "-segment_time_delta {0:F3}", startTime);
- }
-
var segmentFormat = GetSegmentFileExtension(state.Request).TrimStart('.');
if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
{
@@ -980,7 +1011,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
return string.Format(
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f hls -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
inputModifier,
EncodingHelper.GetInputArgument(state, encodingOptions),
threads,
@@ -988,11 +1019,10 @@ namespace MediaBrowser.Api.Playback.Hls
GetVideoArguments(state, encodingOptions),
GetAudioArguments(state, encodingOptions),
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
+ segmentFormat,
startNumberParam,
- outputPath,
outputTsArg,
- timeDeltaParam,
- segmentFormat
+ outputPath
).Trim();
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
index 6a2c7ae03..7487f2b64 100644
--- a/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
+++ b/MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
@@ -97,7 +97,7 @@ namespace MediaBrowser.Api.Playback.Hls
public Task<object> Get(GetHlsPlaylistLegacy request)
{
var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
+ file = Path.Combine(_appPaths.TranscodePath, file);
return GetFileResult(file, file);
}
@@ -116,7 +116,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- var transcodeFolderPath = _config.ApplicationPaths.TranscodingTempPath;
+ var transcodeFolderPath = _config.ApplicationPaths.TranscodePath;
file = Path.Combine(transcodeFolderPath, file);
var normalizedPlaylistId = request.PlaylistId;
@@ -136,7 +136,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
// TODO: Deprecate with new iOS app
var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
- file = Path.Combine(_appPaths.TranscodingTempPath, file);
+ file = Path.Combine(_appPaths.TranscodePath, file);
return ResultFactory.GetStaticFileResult(Request, file, FileShareMode.ReadWrite);
}
diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs
index 76392e27c..6caf3b480 100644
--- a/MediaBrowser.Api/Session/SessionsService.cs
+++ b/MediaBrowser.Api/Session/SessionsService.cs
@@ -321,7 +321,7 @@ namespace MediaBrowser.Api.Session
DateCreated = DateTime.UtcNow,
DeviceId = _appHost.SystemId,
DeviceName = _appHost.FriendlyName,
- AppVersion = _appHost.ApplicationVersion
+ AppVersion = _appHost.ApplicationVersionString
});
}
diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs
index dca31cd92..4c6804097 100644
--- a/MediaBrowser.Common/Cryptography/PasswordHash.cs
+++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
-using static MediaBrowser.Common.HexHelper;
namespace MediaBrowser.Common.Cryptography
{
@@ -102,13 +101,13 @@ namespace MediaBrowser.Common.Cryptography
// Check if the string also contains a salt
if (splitted.Length - index == 2)
{
- salt = FromHexString(splitted[index++]);
- hash = FromHexString(splitted[index++]);
+ salt = Hex.Decode(splitted[index++]);
+ hash = Hex.Decode(splitted[index++]);
}
else
{
salt = Array.Empty<byte>();
- hash = FromHexString(splitted[index++]);
+ hash = Hex.Decode(splitted[index++]);
}
return new PasswordHash(id, hash, salt, parameters);
@@ -145,11 +144,11 @@ namespace MediaBrowser.Common.Cryptography
if (Salt.Length != 0)
{
str.Append('$')
- .Append(ToHexString(Salt));
+ .Append(Hex.Encode(Salt, false));
}
return str.Append('$')
- .Append(ToHexString(Hash)).ToString();
+ .Append(Hex.Encode(Hash, false)).ToString();
}
}
}
diff --git a/MediaBrowser.Common/Extensions/CollectionExtensions.cs b/MediaBrowser.Common/Extensions/CollectionExtensions.cs
deleted file mode 100644
index 215224398..000000000
--- a/MediaBrowser.Common/Extensions/CollectionExtensions.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-
-namespace MediaBrowser.Common.Extensions
-{
- // The MS CollectionExtensions are only available in netcoreapp
- public static class CollectionExtensions
- {
- public static TValue GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
- {
- dictionary.TryGetValue(key, out var ret);
- return ret;
- }
-
- /// <summary>
- /// Copies all the elements of the current collection to the specified list
- /// starting at the specified destination array index. The index is specified as a 32-bit integer.
- /// </summary>
- /// <param name="source">The current collection that is the source of the elements.</param>
- /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param>
- /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param>
- /// <typeparam name="T"></typeparam>
- public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0)
- {
- for (int i = 0; i < source.Count; i++)
- {
- destination[index + i] = source[i];
- }
- }
-
- /// <summary>
- /// Copies all the elements of the current collection to the specified list
- /// starting at the specified destination array index. The index is specified as a 32-bit integer.
- /// </summary>
- /// <param name="source">The current collection that is the source of the elements.</param>
- /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param>
- /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param>
- /// <typeparam name="T"></typeparam>
- public static void CopyTo<T>(this IReadOnlyCollection<T> source, IList<T> destination, int index = 0)
- {
- foreach (T item in source)
- {
- destination[index++] = item;
- }
- }
- }
-}
diff --git a/MediaBrowser.Common/Extensions/CopyToExtensions.cs b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
new file mode 100644
index 000000000..78a73f07e
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/CopyToExtensions.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Provides <c>CopyTo</c> extensions methods for <see cref="IReadOnlyList{T}" />.
+ /// </summary>
+ public static class CollectionExtensions
+ {
+ /// <summary>
+ /// Copies all the elements of the current collection to the specified list
+ /// starting at the specified destination array index. The index is specified as a 32-bit integer.
+ /// </summary>
+ /// <param name="source">The current collection that is the source of the elements.</param>
+ /// <param name="destination">The list that is the destination of the elements copied from the current collection.</param>
+ /// <param name="index">A 32-bit integer that represents the index in <c>destination</c> at which copying begins.</param>
+ /// <typeparam name="T"></typeparam>
+ public static void CopyTo<T>(this IReadOnlyList<T> source, IList<T> destination, int index = 0)
+ {
+ for (int i = 0; i < source.Count; i++)
+ {
+ destination[index + i] = source[i];
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Hex.cs b/MediaBrowser.Common/Hex.cs
new file mode 100644
index 000000000..b2d9aea3a
--- /dev/null
+++ b/MediaBrowser.Common/Hex.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace MediaBrowser.Common
+{
+ /// <summary>
+ /// Encoding and decoding hex strings.
+ /// </summary>
+ public static class Hex
+ {
+ internal const string HexCharsLower = "0123456789abcdef";
+ internal const string HexCharsUpper = "0123456789ABCDEF";
+
+ internal const int LastHexSymbol = 0x66; // 102: f
+
+ /// <summary>
+ /// Map from an ASCII char to its hex value shifted,
+ /// e.g. <c>b</c> -> 11. 0xFF means it's not a hex symbol.
+ /// </summary>
+ /// <value></value>
+ internal static ReadOnlySpan<byte> HexLookup => new byte[] {
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
+ };
+
+ /// <summary>
+ /// Encodes <c>bytes</c> as a hex string.
+ /// </summary>
+ /// <param name="bytes"></param>
+ /// <param name="lowercase"></param>
+ /// <returns><c>bytes</c> as a hex string.</returns>
+ public static string Encode(ReadOnlySpan<byte> bytes, bool lowercase = true)
+ {
+ var hexChars = lowercase ? HexCharsLower : HexCharsUpper;
+
+ // TODO: use string.Create when it's supports spans
+ // Ref: https://github.com/dotnet/corefx/issues/29120
+ char[] s = new char[bytes.Length * 2];
+ int j = 0;
+ for (int i = 0; i < bytes.Length; i++)
+ {
+ s[j++] = hexChars[bytes[i] >> 4];
+ s[j++] = hexChars[bytes[i] & 0x0f];
+ }
+
+ return new string(s);
+ }
+
+ /// <summary>
+ /// Decodes a hex string into bytes.
+ /// </summary>
+ /// <param name="str">The <see cref="string" />.</param>
+ /// <returns>The decoded bytes.</returns>
+ public static byte[] Decode(ReadOnlySpan<char> str)
+ {
+ if (str.Length == 0)
+ {
+ return Array.Empty<byte>();
+ }
+
+ var unHex = HexLookup;
+
+ int byteLen = str.Length / 2;
+ byte[] bytes = new byte[byteLen];
+ int i = 0;
+ for (int j = 0; j < byteLen; j++)
+ {
+ byte a;
+ byte b;
+ if (str[i] > LastHexSymbol
+ || (a = unHex[str[i++]]) == 0xFF
+ || str[i] > LastHexSymbol
+ || (b = unHex[str[i++]]) == 0xFF)
+ {
+ ThrowArgumentException(nameof(str));
+ break; // Unreachable
+ }
+
+ bytes[j] = (byte)((a * 16) | b);
+ }
+
+ return bytes;
+ }
+
+ [DoesNotReturn]
+ private static void ThrowArgumentException(string paramName)
+ => throw new ArgumentException("Character is not a hex symbol.", paramName);
+ }
+}
diff --git a/MediaBrowser.Common/HexHelper.cs b/MediaBrowser.Common/HexHelper.cs
deleted file mode 100644
index 61007b5b2..000000000
--- a/MediaBrowser.Common/HexHelper.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-
-namespace MediaBrowser.Common
-{
- public static class HexHelper
- {
- public static byte[] FromHexString(string str)
- {
- byte[] bytes = new byte[str.Length / 2];
- for (int i = 0; i < str.Length; i += 2)
- {
- bytes[i / 2] = byte.Parse(str.Substring(i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
- }
-
- return bytes;
- }
-
- public static string ToHexString(byte[] bytes)
- => BitConverter.ToString(bytes).Replace("-", "");
- }
-}
diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs
index c8da100f6..6668e98aa 100644
--- a/MediaBrowser.Common/IApplicationHost.cs
+++ b/MediaBrowser.Common/IApplicationHost.cs
@@ -67,7 +67,13 @@ namespace MediaBrowser.Common
/// Gets the application version.
/// </summary>
/// <value>The application version.</value>
- string ApplicationVersion { get; }
+ Version ApplicationVersion { get; }
+
+ /// <summary>
+ /// Gets the application version.
+ /// </summary>
+ /// <value>The application version.</value>
+ string ApplicationVersionString { get; }
/// <summary>
/// Gets the application user agent.
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index ec98400c8..889fbfa5a 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -21,7 +21,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs
index b3367f71d..524d8f3c6 100644
--- a/MediaBrowser.Common/Updates/IInstallationManager.cs
+++ b/MediaBrowser.Common/Updates/IInstallationManager.cs
@@ -13,87 +13,86 @@ namespace MediaBrowser.Common.Updates
public interface IInstallationManager : IDisposable
{
event EventHandler<InstallationEventArgs> PackageInstalling;
+
event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
+
event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
- event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
- /// <summary>
- /// The completed installations
- /// </summary>
- IEnumerable<InstallationInfo> CompletedInstallations { get; }
+ event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
/// <summary>
- /// Occurs when [plugin uninstalled].
+ /// Occurs when a plugin is uninstalled.
/// </summary>
event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
/// <summary>
- /// Occurs when [plugin updated].
+ /// Occurs when a plugin is updated.
/// </summary>
event EventHandler<GenericEventArgs<(IPlugin, PackageVersionInfo)>> PluginUpdated;
/// <summary>
- /// Occurs when [plugin updated].
+ /// Occurs when a plugin is installed.
/// </summary>
event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
/// <summary>
- /// Gets all available packages.
+ /// Gets the completed installations.
/// </summary>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="withRegistration">if set to <c>true</c> [with registration].</param>
- /// <param name="packageType">Type of the package.</param>
- /// <param name="applicationVersion">The application version.</param>
- /// <returns>Task{List{PackageInfo}}.</returns>
- Task<List<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
- bool withRegistration = true, string packageType = null, Version applicationVersion = null);
+ IEnumerable<InstallationInfo> CompletedInstallations { get; }
/// <summary>
- /// Gets all available packages from a static resource.
+ /// Gets all available packages.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{List{PackageInfo}}.</returns>
- Task<List<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken);
+ /// <returns>Task{IReadOnlyList{PackageInfo}}.</returns>
+ Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default);
/// <summary>
- /// Gets the package.
+ /// Returns all plugins matching the requirements.
/// </summary>
- /// <param name="name">The name.</param>
- /// <param name="guid">The assembly guid</param>
- /// <param name="classification">The classification.</param>
- /// <param name="version">The version.</param>
- /// <returns>Task{PackageVersionInfo}.</returns>
- Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version);
+ /// <param name="availablePackages">The available packages.</param>
+ /// <param name="name">The name of the plugin.</param>
+ /// <param name="guid">The id of the plugin.</param>
+ /// <returns>All plugins matching the requirements.</returns>
+ IEnumerable<PackageInfo> FilterPackages(
+ IEnumerable<PackageInfo> availablePackages,
+ string name = null,
+ Guid guid = default);
/// <summary>
- /// Gets the latest compatible version.
+ /// Returns all compatible versions ordered from newest to oldest.
/// </summary>
- /// <param name="name">The name.</param>
- /// <param name="guid">The assembly guid</param>
- /// <param name="currentServerVersion">The current server version.</param>
- /// <param name="classification">The classification.</param>
- /// <returns>Task{PackageVersionInfo}.</returns>
- Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release);
+ /// <param name="availableVersions">The available version of the plugin.</param>
+ /// <param name="minVersion">The minimum required version of the plugin.</param>
+ /// <param name="classification">The classification of updates.</param>
+ /// <returns>All compatible versions ordered from newest to oldest.</returns>
+ IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ IEnumerable<PackageVersionInfo> availableVersions,
+ Version minVersion = null,
+ PackageVersionClass classification = PackageVersionClass.Release);
/// <summary>
- /// Gets the latest compatible version.
+ /// Returns all compatible versions ordered from newest to oldest.
/// </summary>
/// <param name="availablePackages">The available packages.</param>
/// <param name="name">The name.</param>
- /// <param name="guid">The assembly guid</param>
- /// <param name="currentServerVersion">The current server version.</param>
+ /// <param name="guid">The guid of the plugin.</param>
+ /// <param name="minVersion">The minimum required version of the plugin.</param>
/// <param name="classification">The classification.</param>
- /// <returns>PackageVersionInfo.</returns>
- PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release);
+ /// <returns>All compatible versions ordered from newest to oldest.</returns>
+ IEnumerable<PackageVersionInfo> GetCompatibleVersions(
+ IEnumerable<PackageInfo> availablePackages,
+ string name = null,
+ Guid guid = default,
+ Version minVersion = null,
+ PackageVersionClass classification = PackageVersionClass.Release);
/// <summary>
- /// Gets the available plugin updates.
+ /// Returns the available plugin updates.
/// </summary>
- /// <param name="applicationVersion">The current server version.</param>
- /// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
- Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken);
+ /// <returns>The available plugin updates.</returns>
+ Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
/// <summary>
/// Installs the package.
diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
index a6a9a0783..4f0760746 100644
--- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs
@@ -8,8 +8,11 @@ namespace MediaBrowser.Controller.Entities
public interface ICollectionFolder : IHasCollectionType
{
string Path { get; }
+
string Name { get; }
+
Guid Id { get; }
+
string[] PhysicalLocations { get; }
}
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index fd045f0dd..4ce9ec6f8 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -10,34 +10,36 @@ namespace MediaBrowser.Controller.Entities
{
public class UserView : Folder, IHasCollectionType
{
+ /// <inheritdoc />
public string ViewType { get; set; }
+
+ /// <inheritdoc />
public new Guid DisplayParentId { get; set; }
+ /// <inheritdoc />
public Guid? UserId { get; set; }
public static ITVSeriesManager TVSeriesManager;
+ /// <inheritdoc />
[JsonIgnore]
public string CollectionType => ViewType;
+ /// <inheritdoc />
public override IEnumerable<Guid> GetIdsForAncestorQuery()
{
- var list = new List<Guid>();
-
if (!DisplayParentId.Equals(Guid.Empty))
{
- list.Add(DisplayParentId);
+ yield return DisplayParentId;
}
else if (!ParentId.Equals(Guid.Empty))
{
- list.Add(ParentId);
+ yield return ParentId;
}
else
{
- list.Add(Id);
+ yield return Id;
}
-
- return list;
}
[JsonIgnore]
diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs
index 15d7e9f62..127a31329 100644
--- a/MediaBrowser.Controller/IServerApplicationPaths.cs
+++ b/MediaBrowser.Controller/IServerApplicationPaths.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.Controller
/// Gets the transcoding temporary path.
/// </summary>
/// <value>The transcoding temporary path.</value>
- string TranscodingTempPath { get; }
+ string TranscodePath { get; }
/// <summary>
/// Gets the internal metadata path.
@@ -97,6 +97,6 @@ namespace MediaBrowser.Controller
string ArtistsPath { get; }
- string GetTranscodingTempPath();
+ string GetTranscodePath();
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index c6bca2518..276eb71bc 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -17,7 +17,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index d896a7aef..4dfb27130 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2168,7 +2168,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
&& state.TranscodingType != TranscodingJobType.Progressive
- && state.EnableBreakOnNonKeyFrames(outputVideoCodec))
+ && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
+ && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
{
inputModifier += " -noaccurate_seek";
}
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index a8f8da9b8..71eb62693 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -10,7 +10,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index ad07fdd8e..558ea7d67 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
index 91d946db8..0b6cfe1b7 100644
--- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs
+++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs
@@ -64,10 +64,10 @@ namespace MediaBrowser.Model.Globalization
bool HasUnicodeCategory(string value, UnicodeCategory category);
/// <summary>
- /// Returns the correct <see cref="Cultureinfo" /> for the given language.
+ /// Returns the correct <see cref="CultureInfo" /> for the given language.
/// </summary>
/// <param name="language">The language.</param>
- /// <returns>The correct <see cref="Cultureinfo" /> for the given language.</returns>
+ /// <returns>The correct <see cref="CultureInfo" /> for the given language.</returns>
CultureDto FindLanguageInfo(string language);
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index bd4eeea18..53cd08fbd 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
@@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" />
+ <PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="4.6.0" />
</ItemGroup>
diff --git a/MediaBrowser.Model/Net/SocketReceiveResult.cs b/MediaBrowser.Model/Net/SocketReceiveResult.cs
index cd7a2e55f..3a4ad3738 100644
--- a/MediaBrowser.Model/Net/SocketReceiveResult.cs
+++ b/MediaBrowser.Model/Net/SocketReceiveResult.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Model.Net
public int ReceivedBytes { get; set; }
/// <summary>
- /// The <see cref="IpEndPointInfo"/> the data was received from.
+ /// The <see cref="IPEndPoint"/> the data was received from.
/// </summary>
public IPEndPoint RemoteEndPoint { get; set; }
public IPAddress LocalIPAddress { get; set; }
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 3f73cc4e0..7014a5c87 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -110,9 +110,9 @@ namespace MediaBrowser.Model.System
public string InternalMetadataPath { get; set; }
/// <summary>
- /// Gets or sets the transcoding temporary path.
+ /// Gets or sets the transcode path.
/// </summary>
- /// <value>The transcoding temporary path.</value>
+ /// <value>The transcode path.</value>
public string TranscodingTempPath { get; set; }
/// <summary>
diff --git a/MediaBrowser.Model/Updates/PackageInfo.cs b/MediaBrowser.Model/Updates/PackageInfo.cs
index ff4ed26d3..5dd9c6591 100644
--- a/MediaBrowser.Model/Updates/PackageInfo.cs
+++ b/MediaBrowser.Model/Updates/PackageInfo.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace MediaBrowser.Model.Updates
{
@@ -150,7 +151,7 @@ namespace MediaBrowser.Model.Updates
/// Gets or sets the versions.
/// </summary>
/// <value>The versions.</value>
- public PackageVersionInfo[] versions { get; set; }
+ public IReadOnlyList<PackageVersionInfo> versions { get; set; }
/// <summary>
/// Gets or sets a value indicating whether [enable in application store].
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index ec160a848..ae2102806 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -19,7 +19,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
index 112cbf800..dd5ebf270 100644
--- a/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
+++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbUtils.cs
@@ -1,5 +1,4 @@
using System;
-using System.ComponentModel;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Providers.TV.TheTVDB
{
diff --git a/MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs b/MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs
index 25a211fa8..245162728 100644
--- a/MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs
+++ b/MediaBrowser.Providers/Tmdb/Models/Search/MovieResult.cs
@@ -2,64 +2,64 @@ namespace MediaBrowser.Providers.Tmdb.Models.Search
{
public class MovieResult
{
- /// <summary>
- /// Gets or sets a value indicating whether this <see cref="TmdbMovieSearchResult" /> is adult.
- /// </summary>
- /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
- public bool Adult { get; set; }
- /// <summary>
- /// Gets or sets the backdrop_path.
- /// </summary>
- /// <value>The backdrop_path.</value>
- public string Backdrop_Path { get; set; }
- /// <summary>
- /// Gets or sets the id.
- /// </summary>
- /// <value>The id.</value>
- public int Id { get; set; }
- /// <summary>
- /// Gets or sets the original_title.
- /// </summary>
- /// <value>The original_title.</value>
- public string Original_Title { get; set; }
- /// <summary>
- /// Gets or sets the original_name.
- /// </summary>
- /// <value>The original_name.</value>
- public string Original_Name { get; set; }
- /// <summary>
- /// Gets or sets the release_date.
- /// </summary>
- /// <value>The release_date.</value>
- public string Release_Date { get; set; }
- /// <summary>
- /// Gets or sets the poster_path.
- /// </summary>
- /// <value>The poster_path.</value>
- public string Poster_Path { get; set; }
- /// <summary>
- /// Gets or sets the popularity.
- /// </summary>
- /// <value>The popularity.</value>
- public double Popularity { get; set; }
- /// <summary>
- /// Gets or sets the title.
- /// </summary>
- /// <value>The title.</value>
- public string Title { get; set; }
- /// <summary>
- /// Gets or sets the vote_average.
- /// </summary>
- /// <value>The vote_average.</value>
- public double Vote_Average { get; set; }
- /// <summary>
- /// For collection search results
- /// </summary>
- public string Name { get; set; }
- /// <summary>
- /// Gets or sets the vote_count.
- /// </summary>
- /// <value>The vote_count.</value>
- public int Vote_Count { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this <see cref="MovieResult" /> is adult.
+ /// </summary>
+ /// <value><c>true</c> if adult; otherwise, <c>false</c>.</value>
+ public bool Adult { get; set; }
+ /// <summary>
+ /// Gets or sets the backdrop_path.
+ /// </summary>
+ /// <value>The backdrop_path.</value>
+ public string Backdrop_Path { get; set; }
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ public int Id { get; set; }
+ /// <summary>
+ /// Gets or sets the original_title.
+ /// </summary>
+ /// <value>The original_title.</value>
+ public string Original_Title { get; set; }
+ /// <summary>
+ /// Gets or sets the original_name.
+ /// </summary>
+ /// <value>The original_name.</value>
+ public string Original_Name { get; set; }
+ /// <summary>
+ /// Gets or sets the release_date.
+ /// </summary>
+ /// <value>The release_date.</value>
+ public string Release_Date { get; set; }
+ /// <summary>
+ /// Gets or sets the poster_path.
+ /// </summary>
+ /// <value>The poster_path.</value>
+ public string Poster_Path { get; set; }
+ /// <summary>
+ /// Gets or sets the popularity.
+ /// </summary>
+ /// <value>The popularity.</value>
+ public double Popularity { get; set; }
+ /// <summary>
+ /// Gets or sets the title.
+ /// </summary>
+ /// <value>The title.</value>
+ public string Title { get; set; }
+ /// <summary>
+ /// Gets or sets the vote_average.
+ /// </summary>
+ /// <value>The vote_average.</value>
+ public double Vote_Average { get; set; }
+ /// <summary>
+ /// For collection search results
+ /// </summary>
+ public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the vote_count.
+ /// </summary>
+ /// <value>The vote_count.</value>
+ public int Vote_Count { get; set; }
}
}
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index d2ffd5efc..fadf32b28 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -205,7 +205,7 @@ namespace MediaBrowser.WebDashboard.Api
return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => Task.FromResult(stream));
}
- return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersion, null));
+ return _resultFactory.GetStaticResult(Request, plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator(DashboardUIPath).ModifyHtml("dummy.html", stream, null, _appHost.ApplicationVersionString, null));
}
throw new ResourceNotFoundException();
@@ -342,7 +342,7 @@ namespace MediaBrowser.WebDashboard.Api
cacheDuration = TimeSpan.FromDays(365);
}
- var cacheKey = (_appHost.ApplicationVersion + (localizationCulture ?? string.Empty) + path).GetMD5();
+ var cacheKey = (_appHost.ApplicationVersionString + (localizationCulture ?? string.Empty) + path).GetMD5();
// html gets modified on the fly
if (contentType.StartsWith("text/html", StringComparison.OrdinalIgnoreCase))
@@ -364,7 +364,7 @@ namespace MediaBrowser.WebDashboard.Api
private Task<Stream> GetResourceStream(string basePath, string virtualPath, string localizationCulture)
{
return GetPackageCreator(basePath)
- .GetResource(virtualPath, null, localizationCulture, _appHost.ApplicationVersion);
+ .GetResource(virtualPath, null, localizationCulture, _appHost.ApplicationVersionString);
}
private PackageCreator GetPackageCreator(string basePath)
@@ -400,7 +400,7 @@ namespace MediaBrowser.WebDashboard.Api
CopyDirectory(inputPath, targetPath);
}
- var appVersion = _appHost.ApplicationVersion;
+ var appVersion = _appHost.ApplicationVersionString;
await DumpHtml(packageCreator, inputPath, targetPath, mode, appVersion);
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index a43949367..1d256d689 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -16,7 +16,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index 1ca9e43bb..ecc61a8d8 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -10,7 +10,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index dd4e9f8a6..27c8c1668 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -33,8 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
@@ -59,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Common.Tests", "te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.MediaEncoding.Tests", "tests\Jellyfin.MediaEncoding.Tests\Jellyfin.MediaEncoding.Tests.csproj", "{28464062-0939-4AA7-9F7B-24DDDA61A7C0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Naming.Tests", "tests\Jellyfin.Naming.Tests\Jellyfin.Naming.Tests.csproj", "{3998657B-1CCC-49DD-A19F-275DC8495F57}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -129,10 +129,6 @@ Global
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -165,6 +161,10 @@ Global
{28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28464062-0939-4AA7-9F7B-24DDDA61A7C0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3998657B-1CCC-49DD-A19F-275DC8495F57}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3998657B-1CCC-49DD-A19F-275DC8495F57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -193,5 +193,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{DF194677-DFD3-42AF-9F75-D44D5A416478} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{28464062-0939-4AA7-9F7B-24DDDA61A7C0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {3998657B-1CCC-49DD-A19F-275DC8495F57} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
EndGlobal
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs
deleted file mode 100644
index 1241170c1..000000000
--- a/Mono.Nat/AbstractNatDevice.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net;
-using System.Threading.Tasks;
-
-namespace Mono.Nat
-{
- public abstract class AbstractNatDevice : INatDevice
- {
- private DateTime lastSeen;
-
- protected AbstractNatDevice()
- {
- }
-
- public abstract IPAddress LocalAddress { get; }
-
- public DateTime LastSeen
- {
- get { return lastSeen; }
- set { lastSeen = value; }
- }
-
- public abstract Task CreatePortMap(Mapping mapping);
- }
-}
diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs
deleted file mode 100644
index 54480598d..000000000
--- a/Mono.Nat/Enums/ProtocolType.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-//
-// Copyright (C) 2006 Alan McGovern
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-
-namespace Mono.Nat
-{
- public enum Protocol
- {
- Tcp,
- Udp
- }
-}
diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs
deleted file mode 100644
index 6358a0c29..000000000
--- a/Mono.Nat/EventArgs/DeviceEventArgs.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-//
-// Copyright (C) 2006 Alan McGovern
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-
-namespace Mono.Nat
-{
- public class DeviceEventArgs : EventArgs
- {
- private INatDevice device;
-
- public DeviceEventArgs(INatDevice device)
- {
- this.device = device;
- }
-
- public INatDevice Device
- {
- get { return this.device; }
- }
- }
-}
diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs
deleted file mode 100644
index 6a1509071..000000000
--- a/Mono.Nat/INatDevice.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net;
-using System.Threading.Tasks;
-
-namespace Mono.Nat
-{
- public interface INatDevice
- {
- Task CreatePortMap (Mapping mapping);
-
- IPAddress LocalAddress { get; }
-
- DateTime LastSeen { get; set; }
- }
-}
diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs
deleted file mode 100644
index 95c2f2aa9..000000000
--- a/Mono.Nat/ISearcher.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-// Nicholas Terry <nick.i.terry@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-// Copyright (C) 2014 Nicholas Terry
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net.Sockets;
-using System.Net;
-
-namespace Mono.Nat
-{
- internal interface ISearcher
- {
- event EventHandler<DeviceEventArgs> DeviceFound;
-
- void Search();
- void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
- }
-}
diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs
deleted file mode 100644
index 5b15d4e14..000000000
--- a/Mono.Nat/Mapping.cs
+++ /dev/null
@@ -1,121 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-
-namespace Mono.Nat
-{
- public class Mapping
- {
- private string description;
- private DateTime expiration;
- private int lifetime;
- private int privatePort;
- private Protocol protocol;
- private int publicPort;
-
- public Mapping(Protocol protocol, int privatePort, int publicPort)
- : this (protocol, privatePort, publicPort, 0)
- {
- }
-
- public Mapping(Protocol protocol, int privatePort, int publicPort, int lifetime)
- {
- this.protocol = protocol;
- this.privatePort = privatePort;
- this.publicPort = publicPort;
- this.lifetime = lifetime;
-
- if (lifetime == int.MaxValue)
- this.expiration = DateTime.MaxValue;
- else if (lifetime == 0)
- this.expiration = DateTime.Now;
- else
- this.expiration = DateTime.Now.AddSeconds (lifetime);
- }
-
- public string Description
- {
- get { return description; }
- set { description = value; }
- }
-
- public Protocol Protocol
- {
- get { return protocol; }
- internal set { protocol = value; }
- }
-
- public int PrivatePort
- {
- get { return privatePort; }
- internal set { privatePort = value; }
- }
-
- public int PublicPort
- {
- get { return publicPort; }
- internal set { publicPort = value; }
- }
-
- public int Lifetime
- {
- get { return lifetime; }
- internal set { lifetime = value; }
- }
-
- public DateTime Expiration
- {
- get { return expiration; }
- internal set { expiration = value; }
- }
-
- public bool IsExpired()
- {
- return expiration < DateTime.Now;
- }
-
- public override bool Equals(object obj)
- {
- var other = obj as Mapping;
- return other == null ? false : this.protocol == other.protocol &&
- this.privatePort == other.privatePort && this.publicPort == other.publicPort;
- }
-
- public override int GetHashCode()
- {
- return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode();
- }
-
- public override string ToString()
- {
- return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}",
- this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime );
- }
- }
-}
diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj
deleted file mode 100644
index edfd5c9bb..000000000
--- a/Mono.Nat/Mono.Nat.csproj
+++ /dev/null
@@ -1,17 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
- </ItemGroup>
-
- <ItemGroup>
- <Compile Include="..\SharedVersion.cs" />
- </ItemGroup>
-
- <PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
- <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
- </PropertyGroup>
-
-</Project>
diff --git a/Mono.Nat/NatManager.cs b/Mono.Nat/NatManager.cs
deleted file mode 100644
index 3ed01a6b3..000000000
--- a/Mono.Nat/NatManager.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System;
-using System.Net;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Dlna;
-using Microsoft.Extensions.Logging;
-using System.Linq;
-
-namespace Mono.Nat
-{
- public class NatManager : IDisposable
- {
- public event EventHandler<DeviceEventArgs> DeviceFound;
-
- private List<ISearcher> controllers = new List<ISearcher>();
-
- private ILogger Logger;
- private IHttpClient HttpClient;
-
- public NatManager(ILogger logger, IHttpClient httpClient)
- {
- Logger = logger;
- HttpClient = httpClient;
- }
-
- private object _runSyncLock = new object();
- public void StartDiscovery()
- {
- lock (_runSyncLock)
- {
- if (controllers.Count > 0)
- {
- return;
- }
-
- controllers.Add(new PmpSearcher(Logger));
-
- foreach (var searcher in controllers)
- {
- searcher.DeviceFound += Searcher_DeviceFound;
- }
- }
- }
-
- public void StopDiscovery()
- {
- lock (_runSyncLock)
- {
- var disposables = controllers.OfType<IDisposable>().ToList();
- controllers.Clear();
-
- foreach (var disposable in disposables)
- {
- disposable.Dispose();
- }
- }
- }
-
- public void Dispose()
- {
- StopDiscovery();
- }
-
- public Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
- {
- switch (protocol)
- {
- case NatProtocol.Upnp:
- var searcher = new UpnpSearcher(Logger, HttpClient);
- searcher.DeviceFound += Searcher_DeviceFound;
- return searcher.Handle(localAddress, deviceInfo, endpoint);
- default:
- throw new ArgumentException("Unexpected protocol: " + protocol);
- }
- }
-
- private void Searcher_DeviceFound(object sender, DeviceEventArgs e)
- {
- if (DeviceFound != null)
- {
- DeviceFound(sender, e);
- }
- }
- }
-}
diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs
deleted file mode 100644
index 2768f133c..000000000
--- a/Mono.Nat/NatProtocol.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Mono.Nat
-{
- public enum NatProtocol
- {
- Upnp = 0,
- Pmp = 1
- }
-}
diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs
deleted file mode 100644
index 83fa8e07c..000000000
--- a/Mono.Nat/Pmp/PmpConstants.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-//
-// Authors:
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-
-namespace Mono.Nat.Pmp
-{
- internal static class PmpConstants
- {
- public const byte Version = (byte)0;
-
- public const byte OperationCode = (byte)0;
- public const byte OperationCodeUdp = (byte)1;
- public const byte OperationCodeTcp = (byte)2;
- public const byte ServerNoop = (byte)128;
-
- public const int ClientPort = 5350;
- public const int ServerPort = 5351;
-
- public const int RetryDelay = 250;
- public const int RetryAttempts = 9;
-
- public const int RecommendedLeaseTime = 60 * 60;
- public const int DefaultLeaseTime = RecommendedLeaseTime;
-
- public const short ResultCodeSuccess = 0;
- public const short ResultCodeUnsupportedVersion = 1;
- public const short ResultCodeNotAuthorized = 2;
- public const short ResultCodeNetworkFailure = 3;
- public const short ResultCodeOutOfResources = 4;
- public const short ResultCodeUnsupportedOperationCode = 5;
- }
-}
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
deleted file mode 100644
index 95bd72a6c..000000000
--- a/Mono.Nat/Pmp/PmpNatDevice.cs
+++ /dev/null
@@ -1,217 +0,0 @@
-//
-// Authors:
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.IO;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Extensions;
-using Microsoft.Extensions.Logging;
-
-namespace Mono.Nat.Pmp
-{
- internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice>
- {
- private IPAddress localAddress;
- private IPAddress publicAddress;
- private ILogger _logger;
-
- internal PmpNatDevice(IPAddress localAddress, IPAddress publicAddress, ILogger logger)
- {
- if (localAddress == null)
- {
- throw new ArgumentNullException(nameof(localAddress));
- }
-
- this.localAddress = localAddress;
- this.publicAddress = publicAddress;
- _logger = logger;
- }
-
- public override IPAddress LocalAddress
- {
- get { return localAddress; }
- }
-
- public override Task CreatePortMap(Mapping mapping)
- {
- return InternalCreatePortMapAsync(mapping, true);
- }
-
- public override bool Equals(object obj)
- {
- var device = obj as PmpNatDevice;
- return (device == null) ? false : this.Equals(device);
- }
-
- public override int GetHashCode()
- {
- return this.publicAddress.GetHashCode();
- }
-
- public bool Equals(PmpNatDevice other)
- {
- return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
- }
-
- private async Task<Mapping> InternalCreatePortMapAsync(Mapping mapping, bool create)
- {
- var package = new List<byte>();
-
- package.Add(PmpConstants.Version);
- package.Add(mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp);
- package.Add(0); //reserved
- package.Add(0); //reserved
- package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)mapping.PrivatePort)));
- package.AddRange(
- BitConverter.GetBytes(create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0));
- package.AddRange(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(mapping.Lifetime)));
-
- try
- {
- byte[] buffer = package.ToArray();
- int attempt = 0;
- int delay = PmpConstants.RetryDelay;
-
- using (var udpClient = new UdpClient())
- {
- var cancellationTokenSource = new CancellationTokenSource();
-
- while (attempt < PmpConstants.RetryAttempts)
- {
- await udpClient.SendAsync(buffer, buffer.Length, new IPEndPoint(LocalAddress, PmpConstants.ServerPort));
-
- if (attempt == 0)
- {
- await Task.Run(() => CreatePortMapListen(udpClient, mapping, cancellationTokenSource.Token));
- }
-
- attempt++;
- delay *= 2;
- await Task.Delay(delay).ConfigureAwait(false);
- }
-
- cancellationTokenSource.Cancel();
- }
- }
- catch (OperationCanceledException)
- {
-
- }
- catch (Exception e)
- {
- string type = create ? "create" : "delete";
- string message = String.Format("Failed to {0} portmap (protocol={1}, private port={2}) {3}",
- type,
- mapping.Protocol,
- mapping.PrivatePort,
- e.Message);
- _logger.LogDebug(message);
- throw e;
- }
-
- return mapping;
- }
-
- private async void CreatePortMapListen(UdpClient udpClient, Mapping mapping, CancellationToken cancellationToken)
- {
- while (!cancellationToken.IsCancellationRequested)
- {
- try
- {
- var result = await udpClient.ReceiveAsync().ConfigureAwait(false);
- var endPoint = result.RemoteEndPoint;
- byte[] data = data = result.Buffer;
-
- if (data.Length < 16)
- continue;
-
- if (data[0] != PmpConstants.Version)
- continue;
-
- var opCode = (byte)(data[1] & 127);
-
- var protocol = Protocol.Tcp;
- if (opCode == PmpConstants.OperationCodeUdp)
- protocol = Protocol.Udp;
-
- short resultCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 2));
- int epoch = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 4));
-
- short privatePort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 8));
- short publicPort = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 10));
-
- var lifetime = (uint)IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 12));
-
- if (privatePort < 0 || publicPort < 0 || resultCode != PmpConstants.ResultCodeSuccess)
- {
- var errors = new[]
- {
- "Success",
- "Unsupported Version",
- "Not Authorized/Refused (e.g. box supports mapping, but user has turned feature off)"
- ,
- "Network Failure (e.g. NAT box itself has not obtained a DHCP lease)",
- "Out of resources (NAT box cannot create any more mappings at this time)",
- "Unsupported opcode"
- };
-
- var errorMsg = errors[resultCode];
- _logger.LogDebug("Error in CreatePortMapListen: " + errorMsg);
- return;
- }
-
- if (lifetime == 0) return; //mapping was deleted
-
- //mapping was created
- //TODO: verify that the private port+protocol are a match
- mapping.PublicPort = publicPort;
- mapping.Protocol = protocol;
- mapping.Expiration = DateTime.Now.AddSeconds(lifetime);
- return;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in CreatePortMapListen");
- return;
- }
- }
- }
-
- /// <summary>
- /// Overridden.
- /// </summary>
- /// <returns></returns>
- public override string ToString()
- {
- return String.Format("PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
- this.localAddress, this.publicAddress, this.LastSeen);
- }
- }
-}
diff --git a/Mono.Nat/Pmp/PmpSearcher.cs b/Mono.Nat/Pmp/PmpSearcher.cs
deleted file mode 100644
index 46c2e9d80..000000000
--- a/Mono.Nat/Pmp/PmpSearcher.cs
+++ /dev/null
@@ -1,235 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-// Nicholas Terry <nick.i.terry@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-// Copyright (C) 2014 Nicholas Terry
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net;
-using Mono.Nat.Pmp;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-using System.Threading.Tasks;
-using Microsoft.Extensions.Logging;
-using System.Linq;
-
-namespace Mono.Nat
-{
- internal class PmpSearcher : ISearcher, IDisposable
- {
- private ILogger _logger;
-
- private int timeout = 250;
- private DateTime nextSearch;
- public event EventHandler<DeviceEventArgs> DeviceFound;
-
- public PmpSearcher(ILogger logger)
- {
- _logger = logger;
-
- CreateSocketsAndAddGateways();
- }
-
- public void Dispose()
- {
- var list = sockets.ToList();
- sockets.Clear();
-
- foreach (var s in list)
- {
- using (s)
- {
- s.Close();
- }
- }
- }
-
- private List<UdpClient> sockets;
- private Dictionary<UdpClient, List<IPEndPoint>> gatewayLists;
-
- private void CreateSocketsAndAddGateways()
- {
- sockets = new List<UdpClient>();
- gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>();
-
- try
- {
- foreach (var n in NetworkInterface.GetAllNetworkInterfaces())
- {
- if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown)
- continue;
- IPInterfaceProperties properties = n.GetIPProperties();
- var gatewayList = new List<IPEndPoint>();
-
- foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses)
- {
- if (gateway.Address.AddressFamily == AddressFamily.InterNetwork)
- {
- gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort));
- }
- }
- if (gatewayList.Count == 0)
- {
- /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */
- foreach (var gw2 in properties.DnsAddresses)
- {
- if (gw2.AddressFamily == AddressFamily.InterNetwork)
- {
- gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort));
- }
- }
- foreach (UnicastIPAddressInformation unicast in properties.UnicastAddresses)
- {
- if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred
- && unicast.AddressPreferredLifetime != UInt32.MaxValue
- && */unicast.Address.AddressFamily == AddressFamily.InterNetwork)
- {
- var bytes = unicast.Address.GetAddressBytes();
- bytes[3] = 1;
- gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort));
- }
- }
- }
-
- if (gatewayList.Count > 0)
- {
- foreach (var address in properties.UnicastAddresses)
- {
- if (address.Address.AddressFamily == AddressFamily.InterNetwork)
- {
- UdpClient client;
-
- try
- {
- client = new UdpClient(new IPEndPoint(address.Address, 0));
- }
- catch (SocketException)
- {
- continue; // Move on to the next address.
- }
-
- gatewayLists.Add(client, gatewayList);
- sockets.Add(client);
- }
- }
- }
- }
- }
- catch (Exception)
- {
- // NAT-PMP does not use multicast, so there isn't really a good fallback.
- }
- }
-
- public async void Search()
- {
- foreach (UdpClient s in sockets)
- {
- try
- {
- await Search(s).ConfigureAwait(false);
- }
- catch
- {
- // Ignore any search errors
- }
- }
- }
-
- async Task Search(UdpClient client)
- {
- // Sort out the time for the next search first. The spec says the
- // timeout should double after each attempt. Once it reaches 64 seconds
- // (and that attempt fails), assume no devices available
- nextSearch = DateTime.Now.AddMilliseconds(timeout);
- timeout *= 2;
-
- // We've tried 9 times as per spec, try searching again in 5 minutes
- if (timeout == 128 * 1000)
- {
- timeout = 250;
- nextSearch = DateTime.Now.AddMinutes(10);
- return;
- }
-
- // The nat-pmp search message. Must be sent to GatewayIP:53531
- byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
- foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
- {
- await client.SendAsync(buffer, buffer.Length, gatewayEndpoint).ConfigureAwait(false);
- }
- }
-
- bool IsSearchAddress(IPAddress address)
- {
- foreach (var gatewayList in gatewayLists.Values)
- foreach (var gatewayEndpoint in gatewayList)
- if (gatewayEndpoint.Address.Equals(address))
- return true;
- return false;
- }
-
- public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
- {
- if (!IsSearchAddress(endpoint.Address))
- return;
- if (response.Length != 12)
- return;
- if (response[0] != PmpConstants.Version)
- return;
- if (response[1] != PmpConstants.ServerNoop)
- return;
- int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
- if (errorcode != 0)
- _logger.LogDebug("Non zero error: {0}", errorcode);
-
- var publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
- nextSearch = DateTime.Now.AddMinutes(5);
- timeout = 250;
-
- OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp, _logger)));
- }
-
- public DateTime NextSearch
- {
- get { return nextSearch; }
- }
- private void OnDeviceFound(DeviceEventArgs args)
- {
- if (DeviceFound != null)
- DeviceFound(this, args);
- }
-
- public NatProtocol Protocol
- {
- get { return NatProtocol.Pmp; }
- }
- }
-}
diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs
deleted file mode 100644
index dc47f2ffe..000000000
--- a/Mono.Nat/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Mono.Nat")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin Server")]
-[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: NeutralResourcesLanguage("en")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
deleted file mode 100644
index f619f5ca4..000000000
--- a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-//
-// Copyright (C) 2006 Alan McGovern
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Net;
-using MediaBrowser.Common.Net;
-
-namespace Mono.Nat.Upnp
-{
- internal class GetServicesMessage : MessageBase
- {
- private string _servicesDescriptionUrl;
- private EndPoint _hostAddress;
-
- public GetServicesMessage(string description, EndPoint hostAddress)
- : base(null)
- {
- if (string.IsNullOrEmpty(description))
- {
- throw new ArgumentException("Description is null/empty", nameof(description));
- }
-
- this._servicesDescriptionUrl = description;
- this._hostAddress = hostAddress ?? throw new ArgumentNullException(nameof(hostAddress));
- }
-
- public override string Method => "GET";
-
- public override HttpRequestOptions Encode()
- {
- var req = new HttpRequestOptions()
- {
- Url = $"http://{this._hostAddress}{this._servicesDescriptionUrl}"
- };
-
- req.RequestHeaders.Add("ACCEPT-LANGUAGE", "en");
-
- return req;
- }
- }
-}
diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
deleted file mode 100644
index 7d6844e32..000000000
--- a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-//
-// Copyright (C) 2006 Alan McGovern
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System.Net;
-using System.IO;
-using System.Globalization;
-using System.Text;
-using System.Xml;
-using MediaBrowser.Common.Net;
-
-namespace Mono.Nat.Upnp
-{
- internal class CreatePortMappingMessage : MessageBase
- {
- #region Private Fields
-
- private IPAddress localIpAddress;
- private Mapping mapping;
-
- #endregion
-
-
- #region Constructors
- public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device)
- : base(device)
- {
- this.mapping = mapping;
- this.localIpAddress = localIpAddress;
- }
- #endregion
-
- public override HttpRequestOptions Encode()
- {
- var culture = CultureInfo.InvariantCulture;
-
- var builder = new StringBuilder(256);
- XmlWriter writer = CreateWriter(builder);
-
- WriteFullElement(writer, "NewRemoteHost", string.Empty);
- WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture));
- WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
- WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture));
- WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString());
- WriteFullElement(writer, "NewEnabled", "1");
- WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description);
- WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString());
-
- writer.Flush();
- return CreateRequest("AddPortMapping", builder.ToString());
- }
- }
-}
diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
deleted file mode 100644
index d47241d4a..000000000
--- a/Mono.Nat/Upnp/Messages/UpnpMessage.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-//
-// Copyright (C) 2006 Alan McGovern
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System.Xml;
-using System.Text;
-using MediaBrowser.Common.Net;
-
-namespace Mono.Nat.Upnp
-{
- internal abstract class MessageBase
- {
- protected UpnpNatDevice device;
-
- protected MessageBase(UpnpNatDevice device)
- {
- this.device = device;
- }
-
- protected HttpRequestOptions CreateRequest(string upnpMethod, string methodParameters)
- {
- var req = new HttpRequestOptions()
- {
- Url = $"http://{this.device.HostEndPoint}{this.device.ControlUrl}",
- EnableKeepAlive = false,
- RequestContentType = "text/xml",
- RequestContent = "<s:Envelope "
- + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
- + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
- + "<s:Body>"
- + "<u:" + upnpMethod + " "
- + "xmlns:u=\"" + device.ServiceType + "\">"
- + methodParameters
- + "</u:" + upnpMethod + ">"
- + "</s:Body>"
- + "</s:Envelope>\r\n\r\n"
- };
-
- req.RequestHeaders.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\"");
-
- return req;
- }
-
- public abstract HttpRequestOptions Encode();
-
- public virtual string Method => "POST";
-
- protected void WriteFullElement(XmlWriter writer, string element, string value)
- {
- writer.WriteStartElement(element);
- writer.WriteString(value);
- writer.WriteEndElement();
- }
-
- protected XmlWriter CreateWriter(StringBuilder sb)
- {
- var settings = new XmlWriterSettings();
- settings.ConformanceLevel = ConformanceLevel.Fragment;
- return XmlWriter.Create(sb, settings);
- }
- }
-}
diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
deleted file mode 100644
index 3b54c4680..000000000
--- a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-// Nicholas Terry <nick.i.terry@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-// Copyright (C) 2014 Nicholas Terry
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Net;
-using Mono.Nat.Upnp;
-using System.Diagnostics;
-using System.Net.Sockets;
-using System.Net.NetworkInformation;
-using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Dlna;
-using System.Threading.Tasks;
-
-namespace Mono.Nat
-{
- internal class UpnpSearcher : ISearcher
- {
- public event EventHandler<DeviceEventArgs> DeviceFound;
-
- private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
-
- public UpnpSearcher(ILogger logger, IHttpClient httpClient)
- {
- _logger = logger;
- _httpClient = httpClient;
- }
-
- public void Search()
- {
- }
-
- public async Task Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
- {
- if (localAddress == null)
- {
- throw new ArgumentNullException(nameof(localAddress));
- }
-
- try
- {
- /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
- * Any other device type is no good to us for this purpose. See the IGP overview paper
- * page 5 for an overview of device types and their hierarchy.
- * http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
-
- /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
- * version it is and apply the correct URN. */
-
- /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
- * prefix. */
-
- // We have an internet gateway device now
- var d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty, _logger, _httpClient);
-
- await d.GetServicesList().ConfigureAwait(false);
-
- OnDeviceFound(new DeviceEventArgs(d));
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error decoding device response");
- }
- }
-
- public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
- {
- }
-
- private void OnDeviceFound(DeviceEventArgs args)
- {
- if (DeviceFound != null)
- DeviceFound(this, args);
- }
-
- public NatProtocol Protocol
- {
- get { return NatProtocol.Upnp; }
- }
- }
-}
diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs
deleted file mode 100644
index 3ff1eeb90..000000000
--- a/Mono.Nat/Upnp/UpnpNatDevice.cs
+++ /dev/null
@@ -1,267 +0,0 @@
-//
-// Authors:
-// Alan McGovern alan.mcgovern@gmail.com
-// Ben Motmans <ben.motmans@gmail.com>
-//
-// Copyright (C) 2006 Alan McGovern
-// Copyright (C) 2007 Ben Motmans
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-using System;
-using System.Net;
-using System.Xml;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Dlna;
-
-namespace Mono.Nat.Upnp
-{
- public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice>
- {
- private EndPoint hostEndPoint;
- private IPAddress localAddress;
- private string serviceDescriptionUrl;
- private string controlUrl;
- private string serviceType;
- private readonly ILogger _logger;
- private readonly IHttpClient _httpClient;
-
- public override IPAddress LocalAddress
- {
- get { return localAddress; }
- }
-
- internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType, ILogger logger, IHttpClient httpClient)
- {
- if (localAddress == null)
- {
- throw new ArgumentNullException(nameof(localAddress));
- }
-
- this.LastSeen = DateTime.Now;
- this.localAddress = localAddress;
-
- // Split the string at the "location" section so i can extract the ipaddress and service description url
- string locationDetails = deviceInfo.Location.ToString();
- this.serviceType = serviceType;
- _logger = logger;
- _httpClient = httpClient;
-
- // Make sure we have no excess whitespace
- locationDetails = locationDetails.Trim();
-
- // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
- // Are we going to get addresses with the "http://" attached?
- if (locationDetails.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
- {
- _logger.LogDebug("Found device at: {0}", locationDetails);
- // This bit strings out the "http://" from the string
- locationDetails = locationDetails.Substring(7);
-
- this.hostEndPoint = hostEndPoint;
-
- // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
- // and port information
- this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
- }
- else
- {
- _logger.LogDebug("Couldn't decode address. Please send following string to the developer: ");
- }
- }
-
- public async Task GetServicesList()
- {
- // Create a HTTPWebRequest to download the list of services the device offers
- var message = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint);
-
- using (var response = await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false))
- {
- OnServicesReceived(response);
- }
- }
-
- private void OnServicesReceived(HttpResponseInfo response)
- {
- int abortCount = 0;
- int bytesRead = 0;
- byte[] buffer = new byte[10240];
- var servicesXml = new StringBuilder();
- var xmldoc = new XmlDocument();
-
- using (var s = response.Content)
- {
- if (response.StatusCode != HttpStatusCode.OK)
- {
- _logger.LogDebug("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode);
- return; // FIXME: This the best thing to do??
- }
-
- while (true)
- {
- bytesRead = s.Read(buffer, 0, buffer.Length);
- servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
- try
- {
- xmldoc.LoadXml(servicesXml.ToString());
- break;
- }
- catch (XmlException)
- {
- // If we can't receive the entire XML within 500ms, then drop the connection
- // Unfortunately not all routers supply a valid ContentLength (mine doesn't)
- // so this hack is needed to keep testing our recieved data until it gets successfully
- // parsed by the xmldoc. Without this, the code will never pick up my router.
- if (abortCount++ > 50)
- {
- return;
- }
- _logger.LogDebug("{0}: Couldn't parse services list", HostEndPoint);
- System.Threading.Thread.Sleep(10);
- }
- }
-
- var ns = new XmlNamespaceManager(xmldoc.NameTable);
- ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0");
- XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns);
-
- foreach (XmlNode node in nodes)
- {
- //Go through each service there
- foreach (XmlNode service in node.ChildNodes)
- {
- //If the service is a WANIPConnection, then we have what we want
- string type = service["serviceType"].InnerText;
- _logger.LogDebug("{0}: Found service: {1}", HostEndPoint, type);
-
- // TODO: Add support for version 2 of UPnP.
- if (string.Equals(type, "urn:schemas-upnp-org:service:WANPPPConnection:1", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(type, "urn:schemas-upnp-org:service:WANIPConnection:1", StringComparison.OrdinalIgnoreCase))
- {
- this.controlUrl = service["controlURL"].InnerText;
- _logger.LogDebug("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl);
-
- Uri u;
- if (Uri.TryCreate(controlUrl, UriKind.RelativeOrAbsolute, out u))
- {
- if (u.IsAbsoluteUri)
- {
- var old = hostEndPoint;
- IPAddress parsedHostIpAddress;
- if (IPAddress.TryParse(u.Host, out parsedHostIpAddress))
- {
- this.hostEndPoint = new IPEndPoint(parsedHostIpAddress, u.Port);
- //_logger.LogDebug("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint);
- this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length);
- //_logger.LogDebug("{0}: New control url: {1}", HostEndPoint, controlUrl);
- }
- }
- }
- else
- {
- _logger.LogDebug("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
- }
- return;
- }
- }
- }
-
- //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding
- //So we don't invoke the callback, so this device is never added to our lists
- }
- }
-
- /// <summary>
- /// The EndPoint that the device is at
- /// </summary>
- internal EndPoint HostEndPoint
- {
- get { return this.hostEndPoint; }
- }
-
- /// <summary>
- /// The relative url of the xml file that describes the list of services is at
- /// </summary>
- internal string ServiceDescriptionUrl
- {
- get { return this.serviceDescriptionUrl; }
- }
-
- /// <summary>
- /// The relative url that we can use to control the port forwarding
- /// </summary>
- internal string ControlUrl
- {
- get { return this.controlUrl; }
- }
-
- /// <summary>
- /// The service type we're using on the device
- /// </summary>
- public string ServiceType
- {
- get { return serviceType; }
- }
-
- public override async Task CreatePortMap(Mapping mapping)
- {
- var message = new CreatePortMappingMessage(mapping, localAddress, this);
- using (await _httpClient.SendAsync(message.Encode(), message.Method).ConfigureAwait(false))
- {
-
- }
- }
-
- public override bool Equals(object obj)
- {
- var device = obj as UpnpNatDevice;
- return (device == null) ? false : this.Equals((device));
- }
-
-
- public bool Equals(UpnpNatDevice other)
- {
- return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint)
- //&& this.controlUrl == other.controlUrl
- && this.serviceDescriptionUrl == other.serviceDescriptionUrl);
- }
-
- public override int GetHashCode()
- {
- return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode());
- }
-
- /// <summary>
- /// Overridden.
- /// </summary>
- /// <returns></returns>
- public override string ToString()
- {
- //GetExternalIP is blocking and can throw exceptions, can't use it here.
- return String.Format(
- "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}",
- this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen);
- }
- }
-}
diff --git a/RSSDP/RSSDP.csproj b/RSSDP/RSSDP.csproj
index 456a93aa8..9753ae9b1 100644
--- a/RSSDP/RSSDP.csproj
+++ b/RSSDP/RSSDP.csproj
@@ -7,7 +7,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>netstandard2.0</TargetFramework>
+ <TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
diff --git a/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs
new file mode 100644
index 000000000..d9a107b69
--- /dev/null
+++ b/benches/Jellyfin.Common.Benches/HexDecodeBenches.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Globalization;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using MediaBrowser.Common;
+
+namespace Jellyfin.Common.Benches
+{
+ [MemoryDiagnoser]
+ public class HexDecodeBenches
+ {
+ private string _data;
+
+ [Params(0, 10, 100, 1000, 10000, 1000000)]
+ public int N { get; set; }
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ var bytes = new byte[N];
+ new Random(42).NextBytes(bytes);
+ _data = Hex.Encode(bytes);
+ }
+
+ [Benchmark]
+ public byte[] Decode() => Hex.Decode(_data);
+
+ [Benchmark]
+ public byte[] DecodeSubString() => DecodeSubString(_data);
+
+ private static byte[] DecodeSubString(string str)
+ {
+ byte[] bytes = new byte[str.Length / 2];
+ for (int i = 0; i < str.Length; i += 2)
+ {
+ bytes[i / 2] = byte.Parse(
+ str.Substring(i, 2),
+ NumberStyles.HexNumber,
+ CultureInfo.InvariantCulture);
+ }
+
+ return bytes;
+ }
+ }
+}
diff --git a/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs b/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs
new file mode 100644
index 000000000..7abf93c51
--- /dev/null
+++ b/benches/Jellyfin.Common.Benches/HexEncodeBenches.cs
@@ -0,0 +1,32 @@
+using System;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using MediaBrowser.Common;
+
+namespace Jellyfin.Common.Benches
+{
+ [MemoryDiagnoser]
+ public class HexEncodeBenches
+ {
+ private byte[] _data;
+
+ [Params(0, 10, 100, 1000, 10000, 1000000)]
+ public int N { get; set; }
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ _data = new byte[N];
+ new Random(42).NextBytes(_data);
+ }
+
+ [Benchmark]
+ public string HexEncode() => Hex.Encode(_data);
+
+ [Benchmark]
+ public string BitConverterToString() => BitConverter.ToString(_data);
+
+ [Benchmark]
+ public string BitConverterToStringWithReplace() => BitConverter.ToString(_data).Replace("-", "");
+ }
+}
diff --git a/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj
new file mode 100644
index 000000000..4d5046bf9
--- /dev/null
+++ b/benches/Jellyfin.Common.Benches/Jellyfin.Common.Benches.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <OutputType>Exe</OutputType>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BenchmarkDotNet" Version="0.11.5" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/benches/Jellyfin.Common.Benches/Program.cs b/benches/Jellyfin.Common.Benches/Program.cs
new file mode 100644
index 000000000..b218b0dc1
--- /dev/null
+++ b/benches/Jellyfin.Common.Benches/Program.cs
@@ -0,0 +1,14 @@
+using System;
+using BenchmarkDotNet.Running;
+
+namespace Jellyfin.Common.Benches
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ _ = BenchmarkRunner.Run<HexEncodeBenches>();
+ _ = BenchmarkRunner.Run<HexDecodeBenches>();
+ }
+ }
+}
diff --git a/deployment/centos-package-x64/Dockerfile b/deployment/centos-package-x64/Dockerfile
index 855b0a479..04daef93c 100644
--- a/deployment/centos-package-x64/Dockerfile
+++ b/deployment/centos-package-x64/Dockerfile
@@ -3,7 +3,7 @@ FROM centos:7
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/centos-package-x64
ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=2.2
+ARG SDK_VERSION=3.0
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -13,13 +13,12 @@ RUN yum update -y \
&& yum install -y epel-release
# Install build dependencies
-RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel wget git
+RUN yum install -y @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git
# Install recent NodeJS and Yarn
-RUN wget -O- https://raw.githubusercontent.com/creationix/nvm/v0.35.0/install.sh | /bin/bash \
- && source "$HOME/.nvm/nvm.sh" \
- && nvm install v8 \
- && npm install -g yarn
+RUN curl -fSsLo /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
+ && rpm -i https://rpm.nodesource.com/pub_8.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm \
+ && yum install -y yarn
# Install DotNET SDK
RUN rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm \
diff --git a/deployment/centos-package-x64/docker-build.sh b/deployment/centos-package-x64/docker-build.sh
index 18e10661c..62dd144e5 100755
--- a/deployment/centos-package-x64/docker-build.sh
+++ b/deployment/centos-package-x64/docker-build.sh
@@ -8,76 +8,9 @@ set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
-VERSION="$( grep '^Version:' ${SOURCE_DIR}/SOURCES/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-source "$HOME/.nvm/nvm.sh"
-nvm use v8
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Create RPM source archive
-GNU_TAR=1
-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 "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" \
--C ${SOURCE_DIR} ./ || GNU_TAR=0
-
-if [ $GNU_TAR -eq 0 ]; then
- echo "The installed tar binary did not support --transform. Using workaround."
- package_temporary_dir="$( mktemp -d )"
- 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' \
- -czf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \
- -C ${SOURCE_DIR} ./
- echo "Extracting filtered package."
- mkdir -p "${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 -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz"
- echo "Repackaging package into final tarball."
- tar -czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}"
- rm -rf ${package_temporary_dir}
-fi
-
# 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/"
+make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS
+rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
diff --git a/deployment/debian-package-arm64/Dockerfile.amd64 b/deployment/debian-package-arm64/Dockerfile.amd64
index 5644c1470..7a674d029 100644
--- a/deployment/debian-package-arm64/Dockerfile.amd64
+++ b/deployment/debian-package-arm64/Dockerfile.amd64
@@ -12,7 +12,7 @@ ENV ARCH=amd64
# Prepare Debian build environment
RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
+ && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -29,12 +29,6 @@ RUN dpkg --add-architecture arm64 \
&& cd cross-gcc-packages-amd64/cross-gcc-8-arm64 \
&& apt-get install -y gcc-8-source libstdc++-8-dev-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 libssl-dev:arm64 liblttng-ust0:arm64 libstdc++-8-dev:arm64
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/debian-package-arm64/Dockerfile.arm64 b/deployment/debian-package-arm64/Dockerfile.arm64
index 438436766..2b43d70ac 100644
--- a/deployment/debian-package-arm64/Dockerfile.arm64
+++ b/deployment/debian-package-arm64/Dockerfile.arm64
@@ -12,7 +12,7 @@ ENV ARCH=arm64
# Prepare Debian 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 libssl-dev liblttng-ust0
+ && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
diff --git a/deployment/debian-package-arm64/docker-build.sh b/deployment/debian-package-arm64/docker-build.sh
index 7a13bafcb..1c75ece8e 100755
--- a/deployment/debian-package-arm64/docker-build.sh
+++ b/deployment/debian-package-arm64/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -aarm64
diff --git a/deployment/debian-package-armhf/Dockerfile.amd64 b/deployment/debian-package-armhf/Dockerfile.amd64
index b05f10def..2f15d2fcd 100644
--- a/deployment/debian-package-armhf/Dockerfile.amd64
+++ b/deployment/debian-package-armhf/Dockerfile.amd64
@@ -12,7 +12,7 @@ ENV ARCH=amd64
# Prepare Debian build environment
RUN apt-get update \
- && apt-get install -y apt-transport-https debhelper gnupg wget devscripts mmv
+ && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -29,12 +29,6 @@ RUN dpkg --add-architecture armhf \
&& cd cross-gcc-packages-amd64/cross-gcc-8-armhf \
&& apt-get install -y gcc-8-source libstdc++-8-dev-armhf-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip binutils-arm-linux-gnueabihf libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf libssl-dev:armhf liblttng-ust0:armhf libstdc++-8-dev:armhf
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/debian-package-armhf/Dockerfile.armhf b/deployment/debian-package-armhf/Dockerfile.armhf
index 6729d8f38..17a6fa0a1 100644
--- a/deployment/debian-package-armhf/Dockerfile.armhf
+++ b/deployment/debian-package-armhf/Dockerfile.armhf
@@ -12,7 +12,7 @@ ENV ARCH=armhf
# Prepare Debian 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 libssl-dev liblttng-ust0
+ && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev liblttng-ust0
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/3cb1d917-19cc-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/debian-package-armhf/docker-build.sh b/deployment/debian-package-armhf/docker-build.sh
index c48ccb3fb..df35345bd 100755
--- a/deployment/debian-package-armhf/docker-build.sh
+++ b/deployment/debian-package-armhf/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -aarmhf
diff --git a/deployment/debian-package-x64/Dockerfile b/deployment/debian-package-x64/Dockerfile
index 2f97d3944..172bbe8fc 100644
--- a/deployment/debian-package-x64/Dockerfile
+++ b/deployment/debian-package-x64/Dockerfile
@@ -12,7 +12,7 @@ ENV ARCH=amd64
# Prepare Debian 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 libssl-dev libssl1.1 liblttng-ust0
+ && apt-get install -y apt-transport-https debhelper gnupg wget npm devscripts mmv libc6-dev libcurl4-openssl-dev libfontconfig1-dev libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
@@ -21,12 +21,6 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/228832ea-805f-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/debian-package-x64/docker-build.sh b/deployment/debian-package-x64/docker-build.sh
index 97bc45a06..9781879f6 100755
--- a/deployment/debian-package-x64/docker-build.sh
+++ b/deployment/debian-package-x64/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
dpkg-buildpackage -us -uc
diff --git a/deployment/debian-package-x64/pkg-src/control b/deployment/debian-package-x64/pkg-src/control
index af6459604..e8c9d2e23 100644
--- a/deployment/debian-package-x64/pkg-src/control
+++ b/deployment/debian-package-x64/pkg-src/control
@@ -8,7 +8,9 @@ Build-Depends: debhelper (>= 9),
libcurl4-openssl-dev,
libfontconfig1-dev,
libfreetype6-dev,
- libssl-dev
+ libssl-dev,
+ wget,
+ npm | nodejs
Standards-Version: 3.9.4
Homepage: https://jellyfin.media/
Vcs-Git: https://github.org/jellyfin/jellyfin.git
diff --git a/deployment/debian-package-x64/pkg-src/rules b/deployment/debian-package-x64/pkg-src/rules
index 2a5d41a69..c2d57dfb2 100644..100755
--- a/deployment/debian-package-x64/pkg-src/rules
+++ b/deployment/debian-package-x64/pkg-src/rules
@@ -2,6 +2,8 @@
CONFIG := Release
TERM := xterm
SHELL := /bin/bash
+WEB_TARGET := $(CURDIR)/MediaBrowser.WebDashboard/jellyfin-web
+WEB_VERSION := $(shell sed -n -e 's/^version: "\(.*\)"/\1/p' $(CURDIR)/build.yaml)
HOST_ARCH := $(shell arch)
BUILD_ARCH := ${DEB_HOST_MULTIARCH}
@@ -39,12 +41,25 @@ override_dh_auto_test:
override_dh_clistrip:
override_dh_auto_build:
+ echo $(WEB_VERSION)
+ # Clone down and build Web frontend
+ mkdir -p $(WEB_TARGET)
+ wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/v$(WEB_VERSION).tar.gz || wget -O web-src.tgz https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz
+ mkdir -p $(CURDIR)/web
+ tar -xzf web-src.tgz -C $(CURDIR)/web/ --strip 1
+ cd $(CURDIR)/web/ && npm install yarn
+ cd $(CURDIR)/web/ && node_modules/yarn/bin/yarn install
+ mv $(CURDIR)/web/dist/* $(WEB_TARGET)/
+ # Build the application
dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" Jellyfin.Server
override_dh_auto_clean:
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
+ rm -f '$(CURDIR)/web-src.tgz'
rm -rf '$(CURDIR)/usr'
+ rm -rf '$(CURDIR)/web'
+ rm -rf '$(WEB_TARGET)'
# Force the service name to jellyfin even if we're building jellyfin-nightly
override_dh_installinit:
diff --git a/deployment/fedora-package-x64/Dockerfile b/deployment/fedora-package-x64/Dockerfile
index b8226b173..769c62ab2 100644
--- a/deployment/fedora-package-x64/Dockerfile
+++ b/deployment/fedora-package-x64/Dockerfile
@@ -3,7 +3,7 @@ FROM fedora:29
ARG SOURCE_DIR=/jellyfin
ARG PLATFORM_DIR=/jellyfin/deployment/fedora-package-x64
ARG ARTIFACT_DIR=/dist
-ARG SDK_VERSION=2.2
+ARG SDK_VERSION=3.0
# Docker run environment
ENV SOURCE_DIR=/jellyfin
ENV ARTIFACT_DIR=/dist
@@ -12,17 +12,13 @@ ENV ARTIFACT_DIR=/dist
RUN dnf update -y
# Install build dependencies
-RUN dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs wget git
+RUN dnf install -y @buildsys-build rpmdevtools dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel nodejs-yarn
# Install DotNET SDK
RUN dnf copr enable -y @dotnet-sig/dotnet \
&& rpmdev-setuptree \
&& dnf install -y dotnet-sdk-${SDK_VERSION} dotnet-runtime-${SDK_VERSION}
-# Install yarn package manager
-RUN wget -q -O /etc/yum.repos.d/yarn.repo https://dl.yarnpkg.com/rpm/yarn.repo \
- && dnf install -y yarn
-
# Create symlinks and directories
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR}/SPECS \
diff --git a/deployment/fedora-package-x64/docker-build.sh b/deployment/fedora-package-x64/docker-build.sh
index 014f582f0..740e8d35c 100755
--- a/deployment/fedora-package-x64/docker-build.sh
+++ b/deployment/fedora-package-x64/docker-build.sh
@@ -8,74 +8,9 @@ set -o xtrace
# Move to source directory
pushd ${SOURCE_DIR}
-VERSION="$( grep '^Version:' ${SOURCE_DIR}/SOURCES/pkg-src/jellyfin.spec | awk '{ print $NF }' )"
-
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
-# Create RPM source archive
-GNU_TAR=1
-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 "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" \
--C ${SOURCE_DIR} ./ || GNU_TAR=0
-
-if [ $GNU_TAR -eq 0 ]; then
- echo "The installed tar binary did not support --transform. Using workaround."
- package_temporary_dir="$( mktemp -d )"
- 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' \
- -czf "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz" \
- -C ${SOURCE_DIR} ./
- echo "Extracting filtered package."
- mkdir -p "${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 -f "${package_temporary_dir}/jellyfin/jellyfin-${VERSION}.tar.gz"
- echo "Repackaging package into final tarball."
- tar -czf "${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-${VERSION}.tar.gz" -C "${package_temporary_dir}" "jellyfin-${VERSION}"
- rm -rf ${package_temporary_dir}
-fi
-
# 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/"
+make -f .copr/Makefile srpm outdir=/root/rpmbuild/SRPMS
+rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm
# Move the artifacts out
mkdir -p ${ARTIFACT_DIR}/rpm
diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.spec b/deployment/fedora-package-x64/pkg-src/jellyfin.spec
index 0c6bf7180..7118fcf3f 100644
--- a/deployment/fedora-package-x64/pkg-src/jellyfin.spec
+++ b/deployment/fedora-package-x64/pkg-src/jellyfin.spec
@@ -12,28 +12,36 @@ Release: 1%{?dist}
Summary: The Free Software Media Browser
License: GPLv2
URL: https://jellyfin.media
-Source0: %{name}-%{version}.tar.gz
-Source1: jellyfin.service
-Source2: jellyfin.env
-Source3: jellyfin.sudoers
-Source4: restart.sh
-Source5: jellyfin.override.conf
-Source6: jellyfin-firewalld.xml
+# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
+Source0: https://github.com/%{name}/%{name}/archive/%{name}-%{version}.tar.gz
+# Jellyfin Webinterface downloaded by `make -f .copr/Makefile srpm`, real URL ends with `v%{version}.tar.gz`
+Source1: https://github.com/%{name}/%{name}-web/archive/%{name}-web-%{version}.tar.gz
+Source11: jellyfin.service
+Source12: jellyfin.env
+Source13: jellyfin.sudoers
+Source14: restart.sh
+Source15: jellyfin.override.conf
+Source16: jellyfin-firewalld.xml
%{?systemd_requires}
BuildRequires: systemd
Requires(pre): shadow-utils
BuildRequires: libcurl-devel, fontconfig-devel, freetype-devel, openssl-devel, glibc-devel, libicu-devel
+%if 0%{?fedora}
+BuildRequires: nodejs-yarn
+%else
+# Requirements not packaged in main repos
+# From https://rpm.nodesource.com/pub_8.x/el/7/x86_64/
+BuildRequires: nodejs >= 8 yarn
+%endif
Requires: libcurl, fontconfig, freetype, openssl, glibc libicu
# Requirements not packaged in main repos
-# COPR @dotnet-sig/dotnet
-BuildRequires: dotnet-runtime-2.2, dotnet-sdk-2.2
+# COPR @dotnet-sig/dotnet or
+# https://packages.microsoft.com/rhel/7/prod/
+BuildRequires: dotnet-runtime-3.0, dotnet-sdk-3.0
# RPMfusion free
Requires: ffmpeg
-# Fedora has openssl1.1 which is incompatible with dotnet
-%{?fedora:Requires: compat-openssl10}
-
# Disable Automatic Dependency Processing
AutoReqProv: no
@@ -42,7 +50,18 @@ Jellyfin is a free software media system that puts you in control of managing an
%prep
-%autosetup -n %{name}-%{version}
+%autosetup -n %{name}-%{version} -b 0 -b 1
+web_build_dir="$(mktemp -d)"
+web_target="$PWD/MediaBrowser.WebDashboard/jellyfin-web"
+pushd ../jellyfin-web-%{version} || pushd ../jellyfin-web-master
+%if 0%{?fedora}
+nodejs-yarn install
+%else
+yarn install
+%endif
+mkdir -p ${web_target}
+mv dist/* ${web_target}/
+popd
%build
@@ -52,7 +71,7 @@ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
"-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" 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 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/%{name}.service.d/override.conf
%{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/%{name}/logging.json
%{__mkdir} -p %{buildroot}%{_bindir}
tee %{buildroot}%{_bindir}/jellyfin << EOF
@@ -64,11 +83,11 @@ EOF
%{__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}
-%{__install} -D -m 0600 %{SOURCE3} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers
-%{__install} -D -m 0755 %{SOURCE4} %{buildroot}%{_libexecdir}/%{name}/restart.sh
-%{__install} -D -m 0644 %{SOURCE6} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml
+%{__install} -D -m 0644 %{SOURCE11} %{buildroot}%{_unitdir}/%{name}.service
+%{__install} -D -m 0644 %{SOURCE12} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
+%{__install} -D -m 0600 %{SOURCE13} %{buildroot}%{_sysconfdir}/sudoers.d/%{name}-sudoers
+%{__install} -D -m 0755 %{SOURCE14} %{buildroot}%{_libexecdir}/%{name}/restart.sh
+%{__install} -D -m 0644 %{SOURCE16} %{buildroot}%{_prefix}/lib/firewalld/services/%{name}.xml
%files
%{_libdir}/%{name}/jellyfin-web/*
@@ -80,7 +99,7 @@ EOF
%{_libdir}/%{name}/createdump
# Needs 755 else only root can run it since binary build by dotnet is 722
%attr(755,root,root) %{_libdir}/%{name}/jellyfin
-%{_libdir}/%{name}/sosdocsunix.txt
+%{_libdir}/%{name}/SOS_README.md
%{_unitdir}/%{name}.service
%{_libexecdir}/%{name}/restart.sh
%{_prefix}/lib/firewalld/services/%{name}.xml
diff --git a/deployment/ubuntu-package-arm64/Dockerfile.amd64 b/deployment/ubuntu-package-arm64/Dockerfile.amd64
index 838e70d50..44e67a406 100644
--- a/deployment/ubuntu-package-arm64/Dockerfile.amd64
+++ b/deployment/ubuntu-package-arm64/Dockerfile.amd64
@@ -21,6 +21,12 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+# Install npm package manager
+RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
+ && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
+ && apt update \
+ && apt install -y nodejs
+
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
&& export CODENAME="$( lsb_release -c -s )" \
@@ -40,12 +46,6 @@ RUN rm /etc/apt/sources.list \
&& ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
&& apt-get install -y gcc-6-source libstdc++6-arm64-cross binutils-aarch64-linux-gnu bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:arm64 linux-libc-dev:arm64 libgcc1:arm64 libcurl4-openssl-dev:arm64 libfontconfig1-dev:arm64 libfreetype6-dev:arm64 liblttng-ust0:arm64 libstdc++6:arm64 libssl-dev:arm64
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/ubuntu-package-arm64/Dockerfile.arm64 b/deployment/ubuntu-package-arm64/Dockerfile.arm64
index 789dcc15a..58f3d3aaf 100644
--- a/deployment/ubuntu-package-arm64/Dockerfile.arm64
+++ b/deployment/ubuntu-package-arm64/Dockerfile.arm64
@@ -21,6 +21,12 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+# Install npm package manager
+RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
+ && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
+ && apt update \
+ && apt install -y nodejs
+
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/ubuntu-package-arm64/docker-build.sh b/deployment/ubuntu-package-arm64/docker-build.sh
index 7a13bafcb..1c75ece8e 100755
--- a/deployment/ubuntu-package-arm64/docker-build.sh
+++ b/deployment/ubuntu-package-arm64/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -aarm64
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.amd64 b/deployment/ubuntu-package-armhf/Dockerfile.amd64
index d1123e0b6..d69a75b50 100644
--- a/deployment/ubuntu-package-armhf/Dockerfile.amd64
+++ b/deployment/ubuntu-package-armhf/Dockerfile.amd64
@@ -21,6 +21,12 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/69937b49-a877-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
+# Install npm package manager
+RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
+ && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
+ && apt update \
+ && apt install -y nodejs
+
# Prepare the cross-toolchain
RUN rm /etc/apt/sources.list \
&& export CODENAME="$( lsb_release -c -s )" \
@@ -40,12 +46,6 @@ RUN rm /etc/apt/sources.list \
&& ln -fs /usr/share/zoneinfo/America/Toronto /etc/localtime \
&& apt-get install -y gcc-6-source libstdc++6-armhf-cross binutils-arm-linux-gnueabihf bison flex libtool gdb sharutils netbase libcloog-isl-dev libmpc-dev libmpfr-dev libgmp-dev systemtap-sdt-dev autogen expect chrpath zlib1g-dev zip libc6-dev:armhf linux-libc-dev:armhf libgcc1:armhf libcurl4-openssl-dev:armhf libfontconfig1-dev:armhf libfreetype6-dev:armhf liblttng-ust0:armhf libstdc++6:armhf libssl-dev:armhf
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
- && apt update \
- && apt install -y yarn
-
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/ubuntu-package-armhf/Dockerfile.armhf b/deployment/ubuntu-package-armhf/Dockerfile.armhf
index c9e093e51..5d1025080 100644
--- a/deployment/ubuntu-package-armhf/Dockerfile.armhf
+++ b/deployment/ubuntu-package-armhf/Dockerfile.armhf
@@ -21,11 +21,11 @@ RUN wget https://download.visualstudio.microsoft.com/download/pr/d9f37b73-df8d-4
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+# Install npm package manager
+RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
+ && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
&& apt update \
- && apt install -y yarn
+ && apt install -y nodejs
# Link to docker-build script
RUN ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh
diff --git a/deployment/ubuntu-package-armhf/docker-build.sh b/deployment/ubuntu-package-armhf/docker-build.sh
index c48ccb3fb..df35345bd 100755
--- a/deployment/ubuntu-package-armhf/docker-build.sh
+++ b/deployment/ubuntu-package-armhf/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH}
dpkg-buildpackage -us -uc -aarmhf
diff --git a/deployment/ubuntu-package-x64/Dockerfile b/deployment/ubuntu-package-x64/Dockerfile
index 1749d2ad0..99022891b 100644
--- a/deployment/ubuntu-package-x64/Dockerfile
+++ b/deployment/ubuntu-package-x64/Dockerfile
@@ -14,11 +14,11 @@ RUN apt-get update \
&& ln -sf ${PLATFORM_DIR}/docker-build.sh /docker-build.sh \
&& mkdir -p ${SOURCE_DIR} && ln -sf ${PLATFORM_DIR}/pkg-src ${SOURCE_DIR}/debian
-# Install yarn package manager
-RUN wget -q -O- https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
- && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \
+# Install npm package manager
+RUN wget -q -O- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add - \
+ && echo "deb https://deb.nodesource.com/node_8.x $(lsb_release -s -c) main" > /etc/apt/sources.list.d/npm.list \
&& apt update \
- && apt install -y yarn
+ && apt install -y nodejs
VOLUME ${ARTIFACT_DIR}/
diff --git a/deployment/ubuntu-package-x64/docker-build.sh b/deployment/ubuntu-package-x64/docker-build.sh
index 97bc45a06..9781879f6 100755
--- a/deployment/ubuntu-package-x64/docker-build.sh
+++ b/deployment/ubuntu-package-x64/docker-build.sh
@@ -11,20 +11,6 @@ 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
-# Clone down and build Web frontend
-web_build_dir="$( mktemp -d )"
-web_target="${SOURCE_DIR}/MediaBrowser.WebDashboard/jellyfin-web"
-git clone https://github.com/jellyfin/jellyfin-web.git ${web_build_dir}/
-pushd ${web_build_dir}
-if [[ -n ${web_branch} ]]; then
- checkout -b origin/${web_branch}
-fi
-yarn install
-mkdir -p ${web_target}
-mv dist/* ${web_target}/
-popd
-rm -rf ${web_build_dir}
-
# Build DEB
dpkg-buildpackage -us -uc
diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1
index 5e06b9c06..dde6eb8fc 100644
--- a/deployment/windows/build-jellyfin.ps1
+++ b/deployment/windows/build-jellyfin.ps1
@@ -8,6 +8,7 @@ param(
[switch]$GenerateZip,
[string]$InstallLocation = "./dist/jellyfin-win-nsis",
[string]$UXLocation = "../jellyfin-ux",
+ [switch]$InstallTrayApp,
[ValidateSet('Debug','Release')][string]$BuildType = 'Release',
[ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal',
[ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win',
@@ -132,6 +133,23 @@ function Cleanup-NSIS {
Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose
Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose
}
+
+function Install-TrayApp {
+ param(
+ [string]$ResolvedInstallLocation,
+ [string]$Architecture
+ )
+ Write-Verbose "Checking Architecture"
+ if($Architecture -ne 'x64'){
+ Write-Warning "No builds available for your selected architecture of $Architecture"
+ Write-Warning "The tray app will not be available."
+ }else{
+ Write-Verbose "Downloading Tray App and copying to Jellyfin location"
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+ Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose
+ }
+}
+
if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){
Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture"
Build-JellyFin
@@ -144,6 +162,10 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
Write-Verbose "Starting NSSM Install"
Install-NSSM $ResolvedInstallLocation $Architecture
}
+if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){
+ Write-Verbose "Downloading Windows Tray App"
+ Install-TrayApp $ResolvedInstallLocation $Architecture
+}
#Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1
#Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat
Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE
diff --git a/deployment/windows/dialogs/setuptype.nsddef b/deployment/windows/dialogs/setuptype.nsddef
new file mode 100644
index 000000000..b55ceeaaa
--- /dev/null
+++ b/deployment/windows/dialogs/setuptype.nsddef
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+This file was created by NSISDialogDesigner 1.4.4.0
+http://coolsoft.altervista.org/nsisdialogdesigner
+Do not edit manually!
+-->
+<Dialog Name="setuptype" Title="Setup Type" Subtitle="Control how Jellyfin is installed.">
+ <Label Name="InstallasaServiceLabel" Location="12, 115" Size="426, 46" Text="Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." TabIndex="0" />
+ <RadioButton Name="InstallasaService" Location="12, 88" Size="426, 24" Text="Install as a Service (Advanced Users)" TabIndex="1" />
+ <Label Name="BasicInstallLabel" Location="12, 39" Size="426, 46" Text="The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." TabIndex="2" />
+ <RadioButton Name="BasicInstall" Location="12, 12" Size="426, 24" Text="Basic Install (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="3" />
+</Dialog> \ No newline at end of file
diff --git a/deployment/windows/dialogs/setuptype.nsdinc b/deployment/windows/dialogs/setuptype.nsdinc
new file mode 100644
index 000000000..8746ad2cc
--- /dev/null
+++ b/deployment/windows/dialogs/setuptype.nsdinc
@@ -0,0 +1,50 @@
+; =========================================================
+; This file was generated by NSISDialogDesigner 1.4.4.0
+; http://coolsoft.altervista.org/nsisdialogdesigner
+;
+; Do not edit it manually, use NSISDialogDesigner instead!
+; =========================================================
+
+; handle variables
+Var hCtl_setuptype
+Var hCtl_setuptype_InstallasaServiceLabel
+Var hCtl_setuptype_InstallasaService
+Var hCtl_setuptype_BasicInstallLabel
+Var hCtl_setuptype_BasicInstall
+Var hCtl_setuptype_Font1
+
+
+; dialog create function
+Function fnc_setuptype_Create
+
+ ; custom font definitions
+ CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700"
+
+ ; === setuptype (type: Dialog) ===
+ nsDialogs::Create 1018
+ Pop $hCtl_setuptype
+ ${If} $hCtl_setuptype == error
+ Abort
+ ${EndIf}
+ !insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed."
+
+ ; === InstallasaServiceLabel (type: Label) ===
+ ${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares."
+ Pop $hCtl_setuptype_InstallasaServiceLabel
+
+ ; === InstallasaService (type: RadioButton) ===
+ ${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)"
+ Pop $hCtl_setuptype_InstallasaService
+ ${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP}
+
+ ; === BasicInstallLabel (type: Label) ===
+ ${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4."
+ Pop $hCtl_setuptype_BasicInstallLabel
+
+ ; === BasicInstall (type: RadioButton) ===
+ ${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)"
+ Pop $hCtl_setuptype_BasicInstall
+ SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0
+ ${NSD_Check} $hCtl_setuptype_BasicInstall
+
+FunctionEnd
diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi
index e33efde91..5666d30f0 100644
--- a/deployment/windows/jellyfin.nsi
+++ b/deployment/windows/jellyfin.nsi
@@ -16,11 +16,14 @@ ShowUninstDetails show
; Global variables that we'll use
Var _JELLYFINVERSION_
Var _JELLYFINDATADIR_
+ Var _SETUPTYPE_
Var _INSTALLSERVICE_
Var _SERVICESTART_
Var _SERVICEACCOUNTTYPE_
Var _EXISTINGINSTALLATION_
Var _EXISTINGSERVICE_
+ Var _MAKESHORTCUTS_
+ Var _FOLDEREXISTS_
;
!ifdef x64
!define ARCH "x64"
@@ -86,7 +89,12 @@ ShowUninstDetails show
!insertmacro MUI_PAGE_WELCOME
; License Page
!insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL
+
+; Setup Type Page
+ Page custom ShowSetupTypePage SetupTypePage_Config
+
; Components Page
+ !define MUI_PAGE_CUSTOMFUNCTION_PRE HideComponentsPage
!insertmacro MUI_PAGE_COMPONENTS
!define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show
!define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog
@@ -102,6 +110,7 @@ ShowUninstDetails show
!insertmacro MUI_PAGE_DIRECTORY
; Custom Dialogs
+ !include "dialogs\setuptype.nsdinc"
!include "dialogs\service-config.nsdinc"
!include "dialogs\confirmation.nsdinc"
@@ -155,7 +164,9 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer
SetOutPath "$INSTDIR"
+ File "/oname=icon.ico" "${UXPATH}\branding\NSIS\modern-install.ico"
File /r $%InstallLocation%\*
+
; Write the InstallFolder, DataFolder, Network Service info into the registry for later use
WriteRegExpandStr HKLM "${REG_CONFIG_KEY}" "InstallFolder" "$INSTDIR"
@@ -170,7 +181,7 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer
WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"'
WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Uninstall.exe",0'
WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin Project"
- WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.media/"
+ WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.org/"
WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_"
WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1
WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1
@@ -180,12 +191,12 @@ Section "!Jellyfin Server (required)" InstallJellyfinServer
SectionEnd
Section "Jellyfin Server Service" InstallService
-
+${If} $_INSTALLSERVICE_ == "Yes" ; Only run this if we're going to install the service!
ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0
DetailPrint "Jellyfin Server service statuscode, $0"
${If} $0 == 0
InstallRetry:
- ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --datadir \"$_JELLYFINDATADIR_\"' $0
+ ExecWait '"$INSTDIR\nssm.exe" install JellyfinServer "$INSTDIR\jellyfin.exe" --service --datadir \"$_JELLYFINDATADIR_\"' $0
${If} $0 <> 0
!insertmacro ShowError "Could not install the Jellyfin Server service." InstallRetry
${EndIf}
@@ -201,7 +212,7 @@ Section "Jellyfin Server Service" InstallService
DetailPrint "Jellyfin Server Service setting (Application), $0"
ConfigureAppParametersRetry:
- ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --datadir \"$_JELLYFINDATADIR_\"' $0
+ ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppParameters --service --datadir \"$_JELLYFINDATADIR_\"' $0
${If} $0 <> 0
!insertmacro ShowError "Could not configure the Jellyfin Server service." ConfigureAppParametersRetry
${EndIf}
@@ -241,6 +252,15 @@ Section "Jellyfin Server Service" InstallService
DetailPrint "Jellyfin Server service account change, $0"
${EndIf}
+ Sleep 3000
+ ConfigureDefaultAppExit:
+ ExecWait '"$INSTDIR\nssm.exe" set JellyfinServer AppExit Default Exit' $0
+ ${If} $0 <> 0
+ !insertmacro ShowError "Could not configure the Jellyfin Server service app exit action." ConfigureDefaultAppExit
+ ${EndIf}
+ DetailPrint "Jellyfin Server service exit action set, $0"
+${EndIf}
+
SectionEnd
Section "-start service" StartService
@@ -255,6 +275,16 @@ ${AndIf} $_INSTALLSERVICE_ == "Yes"
${EndIf}
SectionEnd
+Section "Create Shortcuts" CreateWinShortcuts
+ ${If} $_MAKESHORTCUTS_ == "Yes"
+ CreateDirectory "$SMPROGRAMS\Jellyfin Server"
+ CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin (View Console).lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMAXIMIZED
+ CreateShortCut "$SMPROGRAMS\Jellyfin Server\Jellyfin Tray App.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0
+ ;CreateShortCut "$DESKTOP\Jellyfin Server.lnk" "$INSTDIR\jellyfin.exe" "--datadir $\"$_JELLYFINDATADIR_$\"" "$INSTDIR\icon.ico" 0 SW_SHOWMINIMIZED
+ CreateShortCut "$DESKTOP\Jellyfin Server\Jellyfin Server.lnk" "$INSTDIR\jellyfintray.exe" "" "$INSTDIR\icon.ico" 0
+ ${EndIf}
+SectionEnd
+
;--------------------------------
;Descriptions
@@ -275,6 +305,7 @@ Section "Uninstall"
ReadRegStr $INSTDIR HKLM "${REG_CONFIG_KEY}" "InstallFolder" ; read the installation folder
ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; read the data folder
+ ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; read the account name
DetailPrint "Jellyfin Install location: $INSTDIR"
DetailPrint "Jellyfin Data folder: $_JELLYFINDATADIR_"
@@ -307,13 +338,18 @@ Section "Uninstall"
Sleep 3000 ; Give time for Windows to catchup
- NoServiceUninstall: ; existing install was present but no service was detected
+ NoServiceUninstall: ; existing install was present but no service was detected. Remove shortcuts if account is set to none
+ ${If} $_SERVICEACCOUNTTYPE_ == "None"
+ RMDir /r "$SMPROGRAMS\Jellyfin Server"
+ Delete "$DESKTOP\Jellyfin Server.lnk"
+ DetailPrint "Removed old shortcuts..."
+ ${EndIf}
Delete "$INSTDIR\*.*"
RMDir /r /REBOOTOK "$INSTDIR\jellyfin-web"
Delete "$INSTDIR\Uninstall.exe"
RMDir /r /REBOOTOK "$INSTDIR"
-
+
DeleteRegKey HKLM "Software\Jellyfin"
DeleteRegKey HKLM "${REG_UNINST_KEY}"
@@ -326,6 +362,7 @@ Function .onInit
StrCpy $_SERVICEACCOUNTTYPE_ "NetworkService"
StrCpy $_EXISTINGINSTALLATION_ "No"
StrCpy $_EXISTINGSERVICE_ "No"
+ StrCpy $_MAKESHORTCUTS_ "No"
SetShellVarContext current
StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server"
@@ -353,6 +390,16 @@ Function .onInit
StrCpy $_EXISTINGINSTALLATION_ "Yes" ; Set our flag to be used later
SectionSetText ${InstallJellyfinServer} "Upgrade Jellyfin Server (required)" ; Change install text to "Upgrade"
+ ; check if service was run using Network Service account
+ ClearErrors
+ ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default
+
+ ClearErrors
+ ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds
+
+ ; Hide sections which will not be needed in case of previous install
+ ; SectionSetText ${InstallService} ""
+
; check if there is a service called Jellyfin, there should be
; hack : nssm statuscode Jellyfin will return non zero return code in case it exists
ExecWait '"$INSTDIR\nssm.exe" statuscode JellyfinServer' $0
@@ -363,18 +410,17 @@ Function .onInit
StrCpy $_EXISTINGSERVICE_ "Yes"
StrCpy $_INSTALLSERVICE_ "Yes"
StrCpy $_SERVICESTART_ "Yes"
+ StrCpy $_MAKESHORTCUTS_ "No"
+ SectionSetText ${CreateWinShortcuts} ""
- ; check if service was run using Network Service account
- ClearErrors
- ReadRegStr $_SERVICEACCOUNTTYPE_ HKLM "${REG_CONFIG_KEY}" "ServiceAccountType" ; in case of error _SERVICEACCOUNTTYPE_ will be NetworkService as default
-
- ClearErrors
- ReadRegStr $_JELLYFINDATADIR_ HKLM "${REG_CONFIG_KEY}" "DataFolder" ; in case of error, the default holds
-
- ; Hide sections which will not be needed in case of previous install
- ; SectionSetText ${InstallService} ""
-
+
NoService: ; existing install was present but no service was detected
+ ${If} $_SERVICEACCOUNTTYPE_ == "None"
+ StrCpy $_SETUPTYPE_ "Basic"
+ StrCpy $_INSTALLSERVICE_ "No"
+ StrCpy $_SERVICESTART_ "No"
+ StrCpy $_MAKESHORTCUTS_ "Yes"
+ ${EndIf}
; Let the user know that we'll upgrade and provide an option to quit.
MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin Server was detected, it'll be upgraded, settings will be retained. \
@@ -383,8 +429,7 @@ Function .onInit
ProceedWithUpgrade:
- NoExisitingInstall:
-; by this time, the variables have been correctly set to reflect previous install details
+ NoExisitingInstall: ; by this time, the variables have been correctly set to reflect previous install details
FunctionEnd
@@ -413,6 +458,25 @@ Function HideConfirmationPage
${EndIf}
FunctionEnd
+Function HideSetupTypePage
+ ${If} $_EXISTINGINSTALLATION_ == "Yes" ; Existing installation detected, so don't ask for SetupType
+ Abort
+ ${EndIf}
+FunctionEnd
+
+Function HideComponentsPage
+ ${If} $_SETUPTYPE_ == "Basic" ; Basic installation chosen, don't show components choice
+ Abort
+ ${EndIf}
+FunctionEnd
+
+; Setup Type dialog show function
+Function ShowSetupTypePage
+ Call HideSetupTypePage
+ Call fnc_setuptype_Create
+ nsDialogs::Show
+FunctionEnd
+
; Service Config dialog show function
Function ShowServiceConfigPage
Call HideServiceConfigPage
@@ -431,6 +495,46 @@ FunctionEnd
Var StartServiceAfterInstall
Var UseNetworkServiceAccount
Var UseLocalSystemAccount
+Var BasicInstall
+
+
+Function SetupTypePage_Config
+${NSD_GetState} $hCtl_setuptype_BasicInstall $BasicInstall
+ IfFileExists "$LOCALAPPDATA\Jellyfin" folderfound foldernotfound ; if the folder exists, use this, otherwise, go with new default
+ folderfound:
+ StrCpy $_FOLDEREXISTS_ "Yes"
+ Goto InstallCheck
+ foldernotfound:
+ StrCpy $_FOLDEREXISTS_ "No"
+ Goto InstallCheck
+
+InstallCheck:
+${If} $BasicInstall == 1
+ StrCpy $_SETUPTYPE_ "Basic"
+ StrCpy $_INSTALLSERVICE_ "No"
+ StrCpy $_SERVICESTART_ "No"
+ StrCpy $_SERVICEACCOUNTTYPE_ "None"
+ StrCpy $_MAKESHORTCUTS_ "Yes"
+ ${If} $_FOLDEREXISTS_ == "Yes"
+ StrCpy $_JELLYFINDATADIR_ "$LOCALAPPDATA\Jellyfin\"
+ ${EndIf}
+${Else}
+ StrCpy $_SETUPTYPE_ "Advanced"
+ StrCpy $_INSTALLSERVICE_ "Yes"
+ StrCpy $_MAKESHORTCUTS_ "No"
+ ${If} $_FOLDEREXISTS_ == "Yes"
+ MessageBox MB_OKCANCEL|MB_ICONINFORMATION "An existing data folder was detected.\
+ $\r$\nBasic Setup is highly recommended.\
+ $\r$\nIf you proceed, you will need to set up Jellyfin again." IDOK GoAhead IDCANCEL GoBack
+ GoBack:
+ Abort
+ ${EndIf}
+ GoAhead:
+ StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\Jellyfin\Server"
+ SectionSetText ${CreateWinShortcuts} ""
+${EndIf}
+
+FunctionEnd
Function ServiceConfigPage_Config
${NSD_GetState} $hCtl_service_config_StartServiceAfterInstall $StartServiceAfterInstall
diff --git a/tests/Jellyfin.Common.Tests/HexTests.cs b/tests/Jellyfin.Common.Tests/HexTests.cs
new file mode 100644
index 000000000..5b578d38c
--- /dev/null
+++ b/tests/Jellyfin.Common.Tests/HexTests.cs
@@ -0,0 +1,19 @@
+using MediaBrowser.Common;
+using Xunit;
+
+namespace Jellyfin.Common.Tests
+{
+ public class HexTests
+ {
+ [Theory]
+ [InlineData("")]
+ [InlineData("00")]
+ [InlineData("01")]
+ [InlineData("000102030405060708090a0b0c0d0e0f")]
+ [InlineData("0123456789abcdef")]
+ public void RoundTripTest(string data)
+ {
+ Assert.Equal(data, Hex.Encode(Hex.Decode(data)));
+ }
+ }
+}
diff --git a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index bb40985a4..aa005b31d 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.1.0" />
diff --git a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
index 5fa86f3bd..03523dbc4 100644
--- a/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
+++ b/tests/Jellyfin.Common.Tests/PasswordHashTests.cs
@@ -1,6 +1,6 @@
+using MediaBrowser.Common;
using MediaBrowser.Common.Cryptography;
using Xunit;
-using static MediaBrowser.Common.HexHelper;
namespace Jellyfin.Common.Tests
{
@@ -15,8 +15,8 @@ namespace Jellyfin.Common.Tests
{
var pass = PasswordHash.Parse(passwordHash);
Assert.Equal(id, pass.Id);
- Assert.Equal(salt, ToHexString(pass.Salt));
- Assert.Equal(hash, ToHexString(pass.Hash));
+ Assert.Equal(salt, Hex.Encode(pass.Salt, false));
+ Assert.Equal(hash, Hex.Encode(pass.Hash, false));
}
[Theory]
diff --git a/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs
new file mode 100644
index 000000000..dd1e04215
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/EpisodePathParserTest.cs
@@ -0,0 +1,55 @@
+using Emby.Naming.Common;
+using Emby.Naming.TV;
+using Xunit;
+
+namespace Jellyfin.Naming.Tests
+{
+ public class EpisodePathParserTest
+ {
+ [Theory]
+ [InlineData("/media/Foo/Foo-S01E01", "Foo", 1, 1)]
+ [InlineData("/media/Foo - S04E011", "Foo", 4, 11)]
+ [InlineData("/media/Foo/Foo s01x01", "Foo", 1, 1)]
+ [InlineData("/media/Foo (2019)/Season 4/Foo (2019).S04E03", "Foo (2019)", 4, 3)]
+ public void ParseEpisodesCorrectly(string path, string name, int season, int episode)
+ {
+ NamingOptions o = new NamingOptions();
+ EpisodePathParser p = new EpisodePathParser(o);
+ var res = p.Parse(path, false);
+
+ Assert.True(res.Success);
+ Assert.Equal(name, res.SeriesName);
+ Assert.Equal(season, res.SeasonNumber);
+ Assert.Equal(episode, res.EpisodeNumber);
+
+ // testing other paths delimeter
+ var res2 = p.Parse(path.Replace('/', '\\'), false);
+ Assert.True(res2.Success);
+ Assert.Equal(name, res2.SeriesName);
+ Assert.Equal(season, res2.SeasonNumber);
+ Assert.Equal(episode, res2.EpisodeNumber);
+ }
+
+ [Theory]
+ [InlineData("/media/Foo/Foo 889", "Foo", 889)]
+ [InlineData("/media/Foo/[Bar] Foo Baz - 11 [1080p]", "Foo Baz", 11)]
+ public void ParseEpisodeWithoutSeason(string path, string name, int episode)
+ {
+ NamingOptions o = new NamingOptions();
+ EpisodePathParser p = new EpisodePathParser(o);
+ var res = p.Parse(path, true, fillExtendedInfo: true);
+
+ Assert.True(res.Success);
+ Assert.Equal(name, res.SeriesName);
+ Assert.Null(res.SeasonNumber);
+ Assert.Equal(episode, res.EpisodeNumber);
+
+ // testing other paths delimeter
+ var res2 = p.Parse(path.Replace('/', '\\'), false, fillExtendedInfo: false);
+ Assert.True(res2.Success);
+ Assert.Equal(name, res2.SeriesName);
+ Assert.Null(res2.SeasonNumber);
+ Assert.Equal(episode, res2.EpisodeNumber);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
new file mode 100644
index 000000000..fe1518131
--- /dev/null
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
+ <PackageReference Include="coverlet.collector" Version="1.1.0" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\..\Emby.Naming\Emby.Naming.csproj" />
+ </ItemGroup>
+
+</Project>