diff options
| author | Max Git <rotvel@gmail.com> | 2020-07-02 23:50:13 +0200 |
|---|---|---|
| committer | Max Git <rotvel@gmail.com> | 2020-07-02 23:50:13 +0200 |
| commit | 3588ee5229b76bca9417813e208e86492e06d609 (patch) | |
| tree | 01131e5776246875ed5a538a71b2bd8a7f1c0c6f | |
| parent | 1c84a31b22fa8708ad3c0c901abda4aac903f28d (diff) | |
| parent | 5caddb242beba3cbaa0c1ca94f7fa07f73309ddb (diff) | |
Merge branch 'master' into feature/ffmpeg-version-check
71 files changed, 883 insertions, 401 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml new file mode 100644 index 000000000..b34253190 --- /dev/null +++ b/.ci/azure-pipelines-package.yml @@ -0,0 +1,131 @@ +jobs: +- job: BuildPackage + displayName: 'Build Packages' + + strategy: + matrix: + CentOS.amd64: + BuildConfiguration: centos.amd64 + Fedora.amd64: + BuildConfiguration: fedora.amd64 + Debian.amd64: + BuildConfiguration: debian.amd64 + Debian.arm64: + BuildConfiguration: debian.arm64 + Debian.armhf: + BuildConfiguration: debian.armhf + Ubuntu.amd64: + BuildConfiguration: ubuntu.amd64 + Ubuntu.arm64: + BuildConfiguration: ubuntu.arm64 + Ubuntu.armhf: + BuildConfiguration: ubuntu.armhf + Linux.amd64: + BuildConfiguration: linux.amd64 + Windows.amd64: + BuildConfiguration: windows.amd64 + MacOS: + BuildConfiguration: macos + Portable: + BuildConfiguration: portable + + pool: + vmImage: 'ubuntu-latest' + + steps: + - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment' + displayName: 'Build Dockerfile' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + + - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)' + displayName: 'Run Dockerfile (unstable)' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + + - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)' + displayName: 'Run Dockerfile (stable)' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Release' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + targetPath: '$(Build.SourcesDirectory)/deployment/dist' + artifactName: 'jellyfin-server-$(BuildConfiguration)' + + - task: CopyFilesOverSSH@0 + displayName: 'Upload artifacts to repository server' + condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + sshEndpoint: repository + sourceFolder: '$(Build.SourcesDirectory)/deployment/dist' + contents: '**' + targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)' + +- job: BuildDocker + displayName: 'Build Docker' + + strategy: + matrix: + amd64: + BuildConfiguration: amd64 + arm64: + BuildConfiguration: arm64 + armhf: + BuildConfiguration: armhf + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: Docker@2 + displayName: 'Push Unstable Image' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + repository: 'jellyfin/jellyfin-server' + command: buildAndPush + buildContext: '.' + Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)' + containerRegistry: Docker Hub + tags: | + unstable-$(Build.BuildNumber)-$(BuildConfiguration) + unstable-$(BuildConfiguration) + + - task: Docker@2 + displayName: 'Push Stable Image' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + inputs: + repository: 'jellyfin/jellyfin-server' + command: buildAndPush + buildContext: '.' + Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)' + containerRegistry: Docker Hub + tags: | + stable-$(Build.BuildNumber)-$(BuildConfiguration) + stable-$(BuildConfiguration) + +- job: CollectArtifacts + displayName: 'Collect Artifacts' + dependsOn: + - BuildPackage + - BuildDocker + condition: and(succeeded('BuildPackage'), succeeded('BuildDocker')) + + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: SSH@0 + displayName: 'Update Unstable Repository' + condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable' + + - task: SSH@0 + displayName: 'Update Stable Repository' + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags') + inputs: + sshEndpoint: repository + runOptions: 'inline' + inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)' diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 3283121e2..c9013b3b8 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -43,3 +43,5 @@ jobs: NugetPackageName: Jellyfin.Common AssemblyFileName: MediaBrowser.Common.dll LinuxImage: 'ubuntu-latest' + + - template: azure-pipelines-package.yml diff --git a/.gitignore b/.gitignore index 46f036ad9..0df7606ce 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,6 @@ ProgramData*/ CorePlugins*/ ProgramData-Server*/ ProgramData-UI*/ -MediaBrowser.WebDashboard/jellyfin-web/** ################# ## Visual Studio @@ -276,4 +275,4 @@ BenchmarkDotNet.Artifacts # Ignore web artifacts from native builds web/ web-src.* -MediaBrowser.WebDashboard/jellyfin-web/ +MediaBrowser.WebDashboard/jellyfin-web diff --git a/.vscode/launch.json b/.vscode/launch.json index 73f347c9f..0f698bfa4 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,9 +1,6 @@ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ + "version": "0.2.0", + "configurations": [ { "name": ".NET Core Launch (console)", "type": "coreclr", @@ -24,5 +21,8 @@ "request": "attach", "processId": "${command:pickProcess}" } - ,] + ], + "env": { + "DOTNET_CLI_TELEMETRY_OPTOUT": "1" + } } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2289fd991..7ddc49d5c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -21,5 +21,10 @@ ], "problemMatcher": "$msCompile" } - ] + ], + "options": { + "env": { + "DOTNET_CLI_TELEMETRY_OPTOUT": "1" + } + } } diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 14267b561..18b6834ef 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -566,10 +566,8 @@ namespace Emby.Server.Implementations serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>)); // TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required - // TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>)); - serviceCollection.AddSingleton<IMediaEncoder>(provider => - ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty)); + serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(); // TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>)); diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs index 8fb9520d6..ac2edc1e2 100644 --- a/Emby.Server.Implementations/Collections/CollectionManager.cs +++ b/Emby.Server.Implementations/Collections/CollectionManager.cs @@ -363,60 +363,4 @@ namespace Emby.Server.Implementations.Collections return results.Values; } } - - /// <summary> - /// The collection manager entry point. - /// </summary> - public sealed class CollectionManagerEntryPoint : IServerEntryPoint - { - private readonly CollectionManager _collectionManager; - private readonly IServerConfigurationManager _config; - private readonly ILogger<CollectionManagerEntryPoint> _logger; - - /// <summary> - /// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class. - /// </summary> - /// <param name="collectionManager">The collection manager.</param> - /// <param name="config">The server configuration manager.</param> - /// <param name="logger">The logger.</param> - public CollectionManagerEntryPoint( - ICollectionManager collectionManager, - IServerConfigurationManager config, - ILogger<CollectionManagerEntryPoint> logger) - { - _collectionManager = (CollectionManager)collectionManager; - _config = config; - _logger = logger; - } - - /// <inheritdoc /> - public async Task RunAsync() - { - if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted) - { - var path = _collectionManager.GetCollectionsFolderPath(); - - if (Directory.Exists(path)) - { - try - { - await _collectionManager.EnsureLibraryFolder(path, true).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error creating camera uploads library"); - } - - _config.Configuration.CollectionsUpgraded = true; - _config.SaveConfiguration(); - } - } - } - - /// <inheritdoc /> - public void Dispose() - { - // Nothing to dispose - } - } } diff --git a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs index 94ee1ced7..a15295fca 100644 --- a/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/Emby.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -109,7 +109,6 @@ namespace Emby.Server.Implementations.Configuration if (!string.IsNullOrWhiteSpace(newPath) && !string.Equals(Configuration.CertificatePath, newPath, StringComparison.Ordinal)) { - // Validate if (!File.Exists(newPath)) { throw new FileNotFoundException( @@ -133,7 +132,6 @@ namespace Emby.Server.Implementations.Configuration if (!string.IsNullOrWhiteSpace(newPath) && !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal)) { - // Validate if (!Directory.Exists(newPath)) { throw new DirectoryNotFoundException( @@ -146,60 +144,5 @@ namespace Emby.Server.Implementations.Configuration EnsureWriteAccess(newPath); } } - - /// <summary> - /// Sets all configuration values to their optimal values. - /// </summary> - /// <returns>If the configuration changed.</returns> - public bool SetOptimalValues() - { - var config = Configuration; - - var changed = false; - - if (!config.EnableCaseSensitiveItemIds) - { - config.EnableCaseSensitiveItemIds = true; - changed = true; - } - - if (!config.SkipDeserializationForBasicTypes) - { - config.SkipDeserializationForBasicTypes = true; - changed = true; - } - - if (!config.EnableSimpleArtistDetection) - { - config.EnableSimpleArtistDetection = true; - changed = true; - } - - if (!config.EnableNormalizedItemByNameIds) - { - config.EnableNormalizedItemByNameIds = true; - changed = true; - } - - if (!config.DisableLiveTvChannelUserDataName) - { - config.DisableLiveTvChannelUserDataName = true; - changed = true; - } - - if (!config.EnableNewOmdbSupport) - { - config.EnableNewOmdbSupport = true; - changed = true; - } - - if (!config.CollectionsUpgraded) - { - config.CollectionsUpgraded = true; - changed = true; - } - - return changed; - } } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 7b7da703b..970f5119c 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -585,7 +585,7 @@ namespace Emby.Server.Implementations.HttpServer if (!string.IsNullOrWhiteSpace(rangeHeader) && totalContentLength.HasValue) { - var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest, _logger) + var hasHeaders = new RangeRequestWriter(rangeHeader, totalContentLength.Value, stream, contentType, isHeadRequest) { OnComplete = options.OnComplete }; @@ -622,8 +622,11 @@ namespace Emby.Server.Implementations.HttpServer /// <summary> /// Adds the caching responseHeaders. /// </summary> - private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration, - bool noCache, DateTime? lastModifiedDate) + private void AddCachingHeaders( + IDictionary<string, string> responseHeaders, + TimeSpan? cacheDuration, + bool noCache, + DateTime? lastModifiedDate) { if (noCache) { diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 540340272..980c2cd3a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System; +using System.Buffers; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -8,52 +9,17 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { public class RangeRequestWriter : IAsyncStreamWriter, IHttpResult { - /// <summary> - /// Gets or sets the source stream. - /// </summary> - /// <value>The source stream.</value> - private Stream SourceStream { get; set; } - - private string RangeHeader { get; set; } - - private bool IsHeadRequest { get; set; } - - private long RangeStart { get; set; } - - private long RangeEnd { get; set; } - - private long RangeLength { get; set; } - - private long TotalContentLength { get; set; } - - public Action OnComplete { get; set; } - - private readonly ILogger _logger; - private const int BufferSize = 81920; - /// <summary> - /// The _options. - /// </summary> private readonly Dictionary<string, string> _options = new Dictionary<string, string>(); - /// <summary> - /// The us culture. - /// </summary> - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - /// <summary> - /// Additional HTTP Headers. - /// </summary> - /// <value>The headers.</value> - public IDictionary<string, string> Headers => _options; + private List<KeyValuePair<long, long?>> _requestedRanges; /// <summary> /// Initializes a new instance of the <see cref="RangeRequestWriter" /> class. @@ -63,8 +29,7 @@ namespace Emby.Server.Implementations.HttpServer /// <param name="source">The source.</param> /// <param name="contentType">Type of the content.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> - /// <param name="logger">The logger instance.</param> - public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest, ILogger logger) + public RangeRequestWriter(string rangeHeader, long contentLength, Stream source, string contentType, bool isHeadRequest) { if (string.IsNullOrEmpty(contentType)) { @@ -74,7 +39,6 @@ namespace Emby.Server.Implementations.HttpServer RangeHeader = rangeHeader; SourceStream = source; IsHeadRequest = isHeadRequest; - this._logger = logger; ContentType = contentType; Headers[HeaderNames.ContentType] = contentType; @@ -85,40 +49,26 @@ namespace Emby.Server.Implementations.HttpServer } /// <summary> - /// Sets the range values. + /// Gets or sets the source stream. /// </summary> - private void SetRangeValues(long contentLength) - { - var requestedRange = RequestedRanges[0]; - - TotalContentLength = contentLength; - - // If the requested range is "0-", we can optimize by just doing a stream copy - if (!requestedRange.Value.HasValue) - { - RangeEnd = TotalContentLength - 1; - } - else - { - RangeEnd = requestedRange.Value.Value; - } - - RangeStart = requestedRange.Key; - RangeLength = 1 + RangeEnd - RangeStart; + /// <value>The source stream.</value> + private Stream SourceStream { get; set; } + private string RangeHeader { get; set; } + private bool IsHeadRequest { get; set; } - Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); - Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + private long RangeStart { get; set; } + private long RangeEnd { get; set; } + private long RangeLength { get; set; } + private long TotalContentLength { get; set; } - if (RangeStart > 0 && SourceStream.CanSeek) - { - SourceStream.Position = RangeStart; - } - } + public Action OnComplete { get; set; } /// <summary> - /// The _requested ranges. + /// Additional HTTP Headers /// </summary> - private List<KeyValuePair<long, long?>> _requestedRanges; + /// <value>The headers.</value> + public IDictionary<string, string> Headers => _options; + /// <summary> /// Gets the requested ranges. /// </summary> @@ -143,12 +93,12 @@ namespace Emby.Server.Implementations.HttpServer if (!string.IsNullOrEmpty(vals[0])) { - start = long.Parse(vals[0], UsCulture); + start = long.Parse(vals[0], CultureInfo.InvariantCulture); } if (!string.IsNullOrEmpty(vals[1])) { - end = long.Parse(vals[1], UsCulture); + end = long.Parse(vals[1], CultureInfo.InvariantCulture); } _requestedRanges.Add(new KeyValuePair<long, long?>(start, end)); @@ -159,6 +109,51 @@ namespace Emby.Server.Implementations.HttpServer } } + public string ContentType { get; set; } + + public IRequest RequestContext { get; set; } + + public object Response { get; set; } + + public int Status { get; set; } + + public HttpStatusCode StatusCode + { + get => (HttpStatusCode)Status; + set => Status = (int)value; + } + + /// <summary> + /// Sets the range values. + /// </summary> + private void SetRangeValues(long contentLength) + { + var requestedRange = RequestedRanges[0]; + + TotalContentLength = contentLength; + + // If the requested range is "0-", we can optimize by just doing a stream copy + if (!requestedRange.Value.HasValue) + { + RangeEnd = TotalContentLength - 1; + } + else + { + RangeEnd = requestedRange.Value.Value; + } + + RangeStart = requestedRange.Key; + RangeLength = 1 + RangeEnd - RangeStart; + + Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture); + Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + + if (RangeStart > 0 && SourceStream.CanSeek) + { + SourceStream.Position = RangeStart; + } + } + public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) { try @@ -174,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer // If the requested range is "0-", we can optimize by just doing a stream copy if (RangeEnd >= TotalContentLength - 1) { - await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false); + await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false); } else { - await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false); + await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false); } } } finally { - if (OnComplete != null) - { - OnComplete(); - } + OnComplete?.Invoke(); } } - private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength) + private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[BufferSize]; - int bytesRead; - while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0) + var array = ArrayPool<byte>.Shared.Rent(BufferSize); + try { - if (bytesRead == 0) + int bytesRead; + while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) { - break; - } + var bytesToCopy = Math.Min(bytesRead, copyLength); - var bytesToCopy = Math.Min(bytesRead, copyLength); + await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false); - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false); + copyLength -= bytesToCopy; - copyLength -= bytesToCopy; - - if (copyLength <= 0) - { - break; + if (copyLength <= 0) + { + break; + } } } - } - - public string ContentType { get; set; } - - public IRequest RequestContext { get; set; } - - public object Response { get; set; } - - public int Status { get; set; } - - public HttpStatusCode StatusCode - { - get => (HttpStatusCode)Status; - set => Status = (int)value; + finally + { + ArrayPool<byte>.Shared.Return(array); + } } } } diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs index e140009ea..77b2c0a69 100644 --- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs +++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Resolvers; @@ -13,19 +14,28 @@ namespace Emby.Server.Implementations.Library public class CoreResolutionIgnoreRule : IResolverIgnoreRule { private readonly ILibraryManager _libraryManager; + private readonly IServerApplicationPaths _serverApplicationPaths; /// <summary> /// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public CoreResolutionIgnoreRule(ILibraryManager libraryManager) + /// <param name="serverApplicationPaths">The server application paths.</param> + public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths) { _libraryManager = libraryManager; + _serverApplicationPaths = serverApplicationPaths; } /// <inheritdoc /> public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent) { + // Don't ignore application folders + if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture)) + { + return false; + } + // Don't ignore top level folders if (fileInfo.IsDirectory && parent is AggregateFolder) { diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs index 8c4098948..6e6ef1359 100644 --- a/Emby.Server.Implementations/Library/IgnorePatterns.cs +++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs @@ -1,3 +1,6 @@ +#nullable enable + +using System; using System.Linq; using DotNet.Globbing; @@ -11,7 +14,7 @@ namespace Emby.Server.Implementations.Library /// <summary> /// Files matching these glob patterns will be ignored. /// </summary> - public static readonly string[] Patterns = new string[] + private static readonly string[] _patterns = { "**/small.jpg", "**/albumart.jpg", @@ -19,32 +22,51 @@ namespace Emby.Server.Implementations.Library // Directories "**/metadata/**", + "**/metadata", "**/ps3_update/**", + "**/ps3_update", "**/ps3_vprm/**", + "**/ps3_vprm", "**/extrafanart/**", + "**/extrafanart", "**/extrathumbs/**", + "**/extrathumbs", "**/.actors/**", + "**/.actors", "**/.wd_tv/**", + "**/.wd_tv", "**/lost+found/**", + "**/lost+found", // WMC temp recording directories that will constantly be written to "**/TempRec/**", + "**/TempRec", "**/TempSBE/**", + "**/TempSBE", // Synology "**/eaDir/**", + "**/eaDir", "**/@eaDir/**", + "**/@eaDir", "**/#recycle/**", + "**/#recycle", // Qnap "**/@Recycle/**", + "**/@Recycle", "**/.@__thumb/**", + "**/.@__thumb", "**/$RECYCLE.BIN/**", + "**/$RECYCLE.BIN", "**/System Volume Information/**", + "**/System Volume Information", "**/.grab/**", + "**/.grab", // Unix hidden files and directories "**/.*/**", + "**/.*", // thumbs.db "**/thumbs.db", @@ -56,19 +78,31 @@ namespace Emby.Server.Implementations.Library private static readonly GlobOptions _globOptions = new GlobOptions { - Evaluation = { + Evaluation = + { CaseInsensitive = true } }; - private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); + private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); /// <summary> /// Returns true if the supplied path should be ignored. /// </summary> - public static bool ShouldIgnore(string path) + /// <param name="path">The path to test.</param> + /// <returns>Whether to ignore the path.</returns> + public static bool ShouldIgnore(ReadOnlySpan<char> path) { - return _globs.Any(g => g.IsMatch(path)); + int len = _globs.Length; + for (int i = 0; i < len; i++) + { + if (_globs[i].IsMatch(path)) + { + return true; + } + } + + return false; } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6a20a015a..77d44e131 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -514,8 +514,8 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, bool allowIgnorePath = true) - => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent, allowIgnorePath: allowIgnorePath); + public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) + => ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent); private BaseItem ResolvePath( FileSystemMetadata fileInfo, @@ -523,8 +523,7 @@ namespace Emby.Server.Implementations.Library IItemResolver[] resolvers, Folder parent = null, string collectionType = null, - LibraryOptions libraryOptions = null, - bool allowIgnorePath = true) + LibraryOptions libraryOptions = null) { if (fileInfo == null) { @@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Library }; // Return null if ignore rules deem that we should do so - if (allowIgnorePath && IgnoreFile(args.FileInfo, args.Parent)) + if (IgnoreFile(args.FileInfo, args.Parent)) { return null; } @@ -713,7 +712,7 @@ namespace Emby.Server.Implementations.Library Directory.CreateDirectory(rootFolderPath); var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? - ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath), allowIgnorePath: false)) + ((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))) .DeepCopy<Folder, AggregateFolder>(); // In case program data folder was moved @@ -795,7 +794,7 @@ namespace Emby.Server.Implementations.Library if (tmpItem == null) { _logger.LogDebug("Creating new userRootFolder with DeepCopy"); - tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath), allowIgnorePath: false)).DeepCopy<Folder, UserRootFolder>(); + tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>(); } // In case program data folder was moved @@ -1894,9 +1893,19 @@ namespace Emby.Server.Implementations.Library } } - ImageDimensions size = _imageProcessor.GetImageDimensions(item, image); - image.Width = size.Width; - image.Height = size.Height; + try + { + ImageDimensions size = _imageProcessor.GetImageDimensions(item, image); + image.Width = size.Width; + image.Height = size.Height; + } + catch (Exception ex) + { + _logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path); + image.Width = 0; + image.Height = 0; + continue; + } try { diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json index d68928fce..4eac8e75d 100644 --- a/Emby.Server.Implementations/Localization/Core/ar.json +++ b/Emby.Server.Implementations/Localization/Core/ar.json @@ -1,5 +1,5 @@ { - "Albums": "ألبومات", + "Albums": "البومات", "AppDeviceValues": "تطبيق: {0}, جهاز: {1}", "Application": "تطبيق", "Artists": "الفنانين", @@ -14,7 +14,7 @@ "FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}", "Favorites": "المفضلة", "Folders": "المجلدات", - "Genres": "الأنواع", + "Genres": "التضنيفات", "HeaderAlbumArtists": "فناني الألبومات", "HeaderCameraUploads": "تحميلات الكاميرا", "HeaderContinueWatching": "استئناف", @@ -50,7 +50,7 @@ "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي", "NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي", "NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا", - "NotificationOptionInstallationFailed": "فشل في التثبيت", + "NotificationOptionInstallationFailed": "فشل التثبيت", "NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد", "NotificationOptionPluginError": "فشل في البرنامج المضاف", "NotificationOptionPluginInstalled": "تم تثبيت الملحق", diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index fc9a10f27..ac96c788c 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -20,7 +20,7 @@ "HeaderContinueWatching": "Seguir viendo", "HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteArtists": "Artistas favoritos", - "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteEpisodes": "Capítulos favoritos", "HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteSongs": "Canciones favoritas", "HeaderLiveTV": "TV en vivo", diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json index 20b37ec9f..4ba324aa1 100644 --- a/Emby.Server.Implementations/Localization/Core/es-MX.json +++ b/Emby.Server.Implementations/Localization/Core/es-MX.json @@ -31,7 +31,7 @@ "ItemAddedWithName": "{0} fue agregado a la biblioteca", "ItemRemovedWithName": "{0} fue removido de la biblioteca", "LabelIpAddressValue": "Dirección IP: {0}", - "LabelRunningTimeValue": "Duración: {0}", + "LabelRunningTimeValue": "Tiempo de reproducción: {0}", "Latest": "Recientes", "MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado", "MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}", diff --git a/Emby.Server.Implementations/Localization/Core/hr.json b/Emby.Server.Implementations/Localization/Core/hr.json index c169a35e7..97c77017b 100644 --- a/Emby.Server.Implementations/Localization/Core/hr.json +++ b/Emby.Server.Implementations/Localization/Core/hr.json @@ -5,23 +5,23 @@ "Artists": "Izvođači", "AuthenticationSucceededWithUserName": "{0} uspješno ovjerena", "Books": "Knjige", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Nova fotografija sa kamere je uploadana iz {0}", "Channels": "Kanali", "ChapterNameValue": "Poglavlje {0}", "Collections": "Kolekcije", "DeviceOfflineWithName": "{0} se odspojilo", "DeviceOnlineWithName": "{0} je spojeno", "FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave za {0}", - "Favorites": "Omiljeni", + "Favorites": "Favoriti", "Folders": "Mape", "Genres": "Žanrovi", - "HeaderAlbumArtists": "Izvođači albuma", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", + "HeaderAlbumArtists": "Izvođači na albumu", + "HeaderCameraUploads": "Uvoz sa kamere", + "HeaderContinueWatching": "Nastavi gledati", "HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteArtists": "Omiljeni izvođači", "HeaderFavoriteEpisodes": "Omiljene epizode", - "HeaderFavoriteShows": "Omiljene emisije", + "HeaderFavoriteShows": "Omiljene serije", "HeaderFavoriteSongs": "Omiljene pjesme", "HeaderLiveTV": "TV uživo", "HeaderNextUp": "Sljedeće je", @@ -34,23 +34,23 @@ "LabelRunningTimeValue": "Vrijeme rada: {0}", "Latest": "Najnovije", "MessageApplicationUpdated": "Jellyfin Server je ažuriran", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server je ažuriran na {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Odjeljak postavka servera {0} je ažuriran", "MessageServerConfigurationUpdated": "Postavke servera su ažurirane", "MixedContent": "Miješani sadržaj", "Movies": "Filmovi", "Music": "Glazba", "MusicVideos": "Glazbeni spotovi", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} neuspješnih instalacija", "NameSeasonNumber": "Sezona {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NameSeasonUnknown": "Nepoznata sezona", + "NewVersionIsAvailable": "Nova verzija Jellyfin servera je dostupna za preuzimanje.", "NotificationOptionApplicationUpdateAvailable": "Dostupno ažuriranje aplikacije", "NotificationOptionApplicationUpdateInstalled": "Instalirano ažuriranje aplikacije", "NotificationOptionAudioPlayback": "Reprodukcija glazbe započeta", "NotificationOptionAudioPlaybackStopped": "Reprodukcija audiozapisa je zaustavljena", "NotificationOptionCameraImageUploaded": "Slike kamere preuzete", - "NotificationOptionInstallationFailed": "Instalacija nije izvršena", + "NotificationOptionInstallationFailed": "Instalacija neuspješna", "NotificationOptionNewLibraryContent": "Novi sadržaj je dodan", "NotificationOptionPluginError": "Dodatak otkazao", "NotificationOptionPluginInstalled": "Dodatak instaliran", @@ -62,7 +62,7 @@ "NotificationOptionVideoPlayback": "Reprodukcija videa započeta", "NotificationOptionVideoPlaybackStopped": "Reprodukcija videozapisa je zaustavljena", "Photos": "Slike", - "Playlists": "Popisi", + "Playlists": "Popis za reprodukciju", "Plugin": "Dodatak", "PluginInstalledWithName": "{0} je instalirano", "PluginUninstalledWithName": "{0} je deinstalirano", @@ -70,15 +70,15 @@ "ProviderValue": "Pružitelj: {0}", "ScheduledTaskFailedWithName": "{0} neuspjelo", "ScheduledTaskStartedWithName": "{0} pokrenuto", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Shows", + "ServerNameNeedsToBeRestarted": "{0} treba biti ponovno pokrenuto", + "Shows": "Serije", "Songs": "Pjesme", "StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Pokušajte ponovo kasnije.", "SubtitleDownloadFailureForItem": "Titlovi prijevoda nisu preuzeti za {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Prijevodi nisu uspješno preuzeti {0} od {1}", "Sync": "Sink.", "System": "Sistem", - "TvShows": "TV Shows", + "TvShows": "Serije", "User": "Korisnik", "UserCreatedWithName": "Korisnik {0} je stvoren", "UserDeletedWithName": "Korisnik {0} je obrisan", @@ -87,10 +87,10 @@ "UserOfflineFromDevice": "{0} se odspojilo od {1}", "UserOnlineFromDevice": "{0} je online od {1}", "UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", + "UserPolicyUpdatedWithName": "Pravila za korisnika su ažurirana za {0}", "UserStartedPlayingItemWithValues": "{0} je pokrenuo {1}", "UserStoppedPlayingItemWithValues": "{0} je zaustavio {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} je dodano u medijsku biblioteku", "ValueSpecialEpisodeName": "Specijal - {0}", "VersionNumber": "Verzija {0}", "TaskRefreshLibraryDescription": "Skenira vašu medijsku knjižnicu sa novim datotekama i osvježuje metapodatke.", @@ -100,5 +100,19 @@ "TaskCleanCacheDescription": "Briše priručne datoteke nepotrebne za sistem.", "TaskCleanCache": "Očisti priručnu memoriju", "TasksApplicationCategory": "Aplikacija", - "TasksMaintenanceCategory": "Održavanje" + "TasksMaintenanceCategory": "Održavanje", + "TaskDownloadMissingSubtitlesDescription": "Pretraživanje interneta za prijevodima koji nedostaju bazirano na konfiguraciji meta podataka.", + "TaskDownloadMissingSubtitles": "Preuzimanje prijevoda koji nedostaju", + "TaskRefreshChannelsDescription": "Osvježava informacije o internet kanalima.", + "TaskRefreshChannels": "Osvježi kanale", + "TaskCleanTranscodeDescription": "Briše transkodirane fajlove starije od jednog dana.", + "TaskCleanTranscode": "Očisti direktorij za transkodiranje", + "TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja za dodatke koji su podešeni da se ažuriraju automatski.", + "TaskUpdatePlugins": "Ažuriraj dodatke", + "TaskRefreshPeopleDescription": "Ažurira meta podatke za glumce i redatelje u vašoj medijskoj biblioteci.", + "TaskRefreshPeople": "Osvježi ljude", + "TaskCleanLogsDescription": "Briši logove koji su stariji od {0} dana.", + "TaskCleanLogs": "Očisti direktorij sa logovima", + "TasksChannelsCategory": "Internet kanali", + "TasksLibraryCategory": "Biblioteka" } diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json index 73fae3931..38c073709 100644 --- a/Emby.Server.Implementations/Localization/Core/ne.json +++ b/Emby.Server.Implementations/Localization/Core/ne.json @@ -58,5 +58,29 @@ "Books": "पुस्तकहरु", "Artists": "कलाकारहरू", "Application": "अनुप्रयोगहरू", - "Albums": "एल्बमहरू" + "Albums": "एल्बमहरू", + "TasksLibraryCategory": "पुस्तकालय", + "TasksApplicationCategory": "अनुप्रयोग", + "TasksMaintenanceCategory": "मर्मत", + "UserPolicyUpdatedWithName": "प्रयोगकर्ता नीति को लागी अद्यावधिक गरिएको छ {0}", + "UserPasswordChangedWithName": "पासवर्ड प्रयोगकर्ताका लागि परिवर्तन गरिएको छ {0}", + "UserOnlineFromDevice": "{0} बाट अनलाइन छ {1}", + "UserOfflineFromDevice": "{0} बाट विच्छेदन भएको छ {1}", + "UserLockedOutWithName": "प्रयोगकर्ता {0} लक गरिएको छ", + "UserDeletedWithName": "प्रयोगकर्ता {0} हटाइएको छ", + "UserCreatedWithName": "प्रयोगकर्ता {0} सिर्जना गरिएको छ", + "User": "प्रयोगकर्ता", + "PluginInstalledWithName": "", + "StartupEmbyServerIsLoading": "Jellyfin सर्भर लोड हुँदैछ। कृपया छिट्टै फेरि प्रयास गर्नुहोस्।", + "Songs": "गीतहरू", + "Shows": "शोहरू", + "ServerNameNeedsToBeRestarted": "{0} लाई पुन: सुरु गर्नु पर्छ", + "ScheduledTaskStartedWithName": "{0} सुरु भयो", + "ScheduledTaskFailedWithName": "{0} असफल", + "ProviderValue": "प्रदायक: {0}", + "Plugin": "प्लगइनहरू", + "Playlists": "प्लेलिस्टहरू", + "Photos": "तस्बिरहरु", + "NotificationOptionVideoPlaybackStopped": "भिडियो प्लेब्याक रोकियो", + "NotificationOptionVideoPlayback": "भिडियो प्लेब्याक सुरु भयो" } diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json index 25c5b9053..5365fff23 100644 --- a/Emby.Server.Implementations/Localization/Core/pt.json +++ b/Emby.Server.Implementations/Localization/Core/pt.json @@ -101,7 +101,8 @@ "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.", "TaskCleanLogs": "Limpar diretório de log", "TaskRefreshLibrary": "Escanear biblioteca de mídias", - "TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.", - "TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.", - "TasksChannelsCategory": "Canais de Internet" + "TaskRefreshChapterImagesDescription": "Cria miniaturas para vídeos que têm capítulos.", + "TaskCleanCacheDescription": "Apaga ficheiros em cache que já não são usados pelo sistema.", + "TasksChannelsCategory": "Canais de Internet", + "TaskRefreshChapterImages": "Extrair Imagens do Capítulo" } diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 177721658..0781a0e33 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -19,6 +19,7 @@ namespace Emby.Server.Implementations.Net var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { + retVal.EnableBroadcast = true; retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1); @@ -46,6 +47,7 @@ namespace Emby.Server.Implementations.Net var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try { + retVal.EnableBroadcast = true; retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4); @@ -112,6 +114,7 @@ namespace Emby.Server.Implementations.Net try { + retVal.EnableBroadcast = true; // retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 75fdedd10..d069d1ada 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -502,7 +502,8 @@ namespace Emby.Server.Implementations.Session Client = appName, DeviceId = deviceId, ApplicationVersion = appVersion, - Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture) + Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture), + ServerId = _appHost.SystemId }; var username = user?.Username; diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 6ec0a4e26..04f134b8b 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -36,7 +36,6 @@ namespace Jellyfin.Api.Controllers public void CompleteWizard() { _config.Configuration.IsStartupWizardCompleted = true; - _config.SetOptimalValues(); _config.SaveConfiguration(); } diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 55c5ef1b1..93c4612b6 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -16,7 +16,7 @@ <PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" /> <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" /> - <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.0" /> + <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" /> </ItemGroup> <ItemGroup> diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index 904114758..ae5c311bf 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -12,6 +12,7 @@ using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; using MediaBrowser.Common; using MediaBrowser.Common.Cryptography; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Drawing; @@ -192,15 +193,15 @@ namespace Jellyfin.Server.Implementations.Users } /// <inheritdoc/> - public void DeleteUser(User user) + public void DeleteUser(Guid userId) { + var dbContext = _dbProvider.CreateContext(); + var user = dbContext.Users.Find(userId); if (user == null) { - throw new ArgumentNullException(nameof(user)); + throw new ResourceNotFoundException(nameof(userId)); } - var dbContext = _dbProvider.CreateContext(); - if (dbContext.Users.Find(user.Id) == null) { throw new ArgumentException(string.Format( @@ -226,9 +227,18 @@ namespace Jellyfin.Server.Implementations.Users CultureInfo.InvariantCulture, "The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Username), - nameof(user)); + nameof(userId)); + } + + // Clear all entities related to the user from the database. + if (user.ProfileImage != null) + { + dbContext.Remove(user.ProfileImage); } + dbContext.RemoveRange(user.Permissions); + dbContext.RemoveRange(user.Preferences); + dbContext.RemoveRange(user.AccessSchedules); dbContext.Users.Remove(user); dbContext.SaveChanges(); OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user)); diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index a26114e77..41a1430d2 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -101,6 +101,11 @@ namespace Jellyfin.Server config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString()); } + if (FFmpegPath != null) + { + config.Add(ConfigurationExtensions.FfmpegPathKey, FFmpegPath); + } + return config; } } diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs index 1e14ea552..88cddfff7 100644 --- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs +++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs @@ -11,21 +11,16 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.SyncPlay { - [Route("/SyncPlay/{SessionId}/NewGroup", "POST", Summary = "Create a new SyncPlay group")] + [Route("/SyncPlay/NewGroup", "POST", Summary = "Create a new SyncPlay group")] [Authenticated] public class SyncPlayNewGroup : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } } - [Route("/SyncPlay/{SessionId}/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] + [Route("/SyncPlay/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")] [Authenticated] public class SyncPlayJoinGroup : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - /// <summary> /// Gets or sets the Group id. /// </summary> @@ -41,63 +36,48 @@ namespace MediaBrowser.Api.SyncPlay public string PlayingItemId { get; set; } } - [Route("/SyncPlay/{SessionId}/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] + [Route("/SyncPlay/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")] [Authenticated] public class SyncPlayLeaveGroup : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } } - [Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")] + [Route("/SyncPlay/ListGroups", "GET", Summary = "List SyncPlay groups")] [Authenticated] public class SyncPlayListGroups : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - /// <summary> /// Gets or sets the filter item id. /// </summary> /// <value>The filter item id.</value> - [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + [ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string FilterItemId { get; set; } } - [Route("/SyncPlay/{SessionId}/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] + [Route("/SyncPlay/PlayRequest", "POST", Summary = "Request play in SyncPlay group")] [Authenticated] public class SyncPlayPlayRequest : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } } - [Route("/SyncPlay/{SessionId}/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] + [Route("/SyncPlay/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")] [Authenticated] public class SyncPlayPauseRequest : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } } - [Route("/SyncPlay/{SessionId}/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] + [Route("/SyncPlay/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")] [Authenticated] public class SyncPlaySeekRequest : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - [ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")] public long PositionTicks { get; set; } } - [Route("/SyncPlay/{SessionId}/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] + [Route("/SyncPlay/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")] [Authenticated] public class SyncPlayBufferingRequest : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - /// <summary> /// Gets or sets the date used to pin PositionTicks in time. /// </summary> @@ -116,13 +96,10 @@ namespace MediaBrowser.Api.SyncPlay public bool BufferingDone { get; set; } } - [Route("/SyncPlay/{SessionId}/UpdatePing", "POST", Summary = "Update session ping")] + [Route("/SyncPlay/UpdatePing", "POST", Summary = "Update session ping")] [Authenticated] public class SyncPlayUpdatePing : IReturnVoid { - [ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] - public string SessionId { get; set; } - [ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")] public double Ping { get; set; } } diff --git a/MediaBrowser.Api/UserService.cs b/MediaBrowser.Api/UserService.cs index 131def554..6e9d788dc 100644 --- a/MediaBrowser.Api/UserService.cs +++ b/MediaBrowser.Api/UserService.cs @@ -365,15 +365,8 @@ namespace MediaBrowser.Api public Task DeleteAsync(DeleteUser request) { - var user = _userManager.GetUserById(request.Id); - - if (user == null) - { - throw new ResourceNotFoundException("User not found"); - } - - _sessionMananger.RevokeUserTokens(user.Id, null); - _userManager.DeleteUser(user); + _userManager.DeleteUser(request.Id); + _sessionMananger.RevokeUserTokens(request.Id, null); return Task.CompletedTask; } diff --git a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs index a5c5e3bcc..43ad04dba 100644 --- a/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs +++ b/MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs @@ -19,7 +19,5 @@ namespace MediaBrowser.Controller.Configuration /// </summary> /// <value>The configuration.</value> ServerConfiguration Configuration { get; } - - bool SetOptimalValues(); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index bf87c0336..25933bc90 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -560,8 +560,6 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// The logger. /// </summary> - public static ILoggerFactory LoggerFactory { get; set; } - public static ILogger<BaseItem> Logger { get; set; } public static ILibraryManager LibraryManager { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index ceca77bc0..1fba8a30f 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -68,7 +68,7 @@ namespace MediaBrowser.Controller.Entities parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent; } - return new UserViewBuilder(UserViewManager, LibraryManager, LoggerFactory.CreateLogger<UserViewBuilder>(), UserDataManager, TVSeriesManager, ConfigurationManager) + return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager) .GetUserItems(parent, this, CollectionType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 061e6001c..cb35d6e32 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.TV; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.Extensions.Logging; @@ -22,7 +23,7 @@ namespace MediaBrowser.Controller.Entities { private readonly IUserViewManager _userViewManager; private readonly ILibraryManager _libraryManager; - private readonly ILogger<UserViewBuilder> _logger; + private readonly ILogger<BaseItem> _logger; private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; private readonly IServerConfigurationManager _config; @@ -30,7 +31,7 @@ namespace MediaBrowser.Controller.Entities public UserViewBuilder( IUserViewManager userViewManager, ILibraryManager libraryManager, - ILogger<UserViewBuilder> logger, + ILogger<BaseItem> logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config) diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs index c0043c0ef..c2932cc4c 100644 --- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs +++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs @@ -24,6 +24,11 @@ namespace MediaBrowser.Controller.Extensions public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration"; /// <summary> + /// The key for the FFmpeg path option. + /// </summary> + public const string FfmpegPathKey = "ffmpeg"; + + /// <summary> /// The key for a setting that indicates whether playlists should allow duplicate entries. /// </summary> public const string PlaylistsAllowDuplicatesKey = "playlists:allowDuplicates"; diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 47c080ebd..9d6646857 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -30,12 +30,10 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="fileInfo">The file information.</param> /// <param name="parent">The parent.</param> - /// <param name="allowIgnorePath">Allow the path to be ignored.</param> /// <returns>BaseItem.</returns> BaseItem ResolvePath( FileSystemMetadata fileInfo, - Folder parent = null, - bool allowIgnorePath = true); + Folder parent = null); /// <summary> /// Resolves a set of files into a list of BaseItem. diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index fe3e4f9e6..e73fe7120 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -111,8 +111,8 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Deletes the specified user. /// </summary> - /// <param name="user">The user to be deleted.</param> - void DeleteUser(User user); + /// <param name="userId">The id of the user to be deleted.</param> + void DeleteUser(Guid userId); /// <summary> /// Resets the password. diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs index d7e337bda..5e38446bc 100644 --- a/MediaBrowser.Controller/Providers/IExternalId.cs +++ b/MediaBrowser.Controller/Providers/IExternalId.cs @@ -1,15 +1,45 @@ using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Controller.Providers { + /// <summary> + /// Represents an identifier for an external provider. + /// </summary> public interface IExternalId { - string Name { get; } + /// <summary> + /// Gets the display name of the provider associated with this ID type. + /// </summary> + string ProviderName { get; } + /// <summary> + /// Gets the unique key to distinguish this provider/type pair. This should be unique across providers. + /// </summary> + // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique. string Key { get; } + /// <summary> + /// Gets the specific media type for this id. This is used to distinguish between the different + /// external id types for providers with multiple ids. + /// A null value indicates there is no specific media type associated with the external id, or this is the + /// default id for the external provider so there is no need to specify a type. + /// </summary> + /// <remarks> + /// This can be used along with the <see cref="ProviderName"/> to localize the external id on the client. + /// </remarks> + ExternalIdMediaType? Type { get; } + + /// <summary> + /// Gets the URL format string for this id. + /// </summary> string UrlFormatString { get; } + /// <summary> + /// Determines whether this id supports a given item type. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>True if this item is supported, otherwise false.</returns> bool Supports(IHasProviderIds item); } } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 36bc11be4..4b088998c 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -109,6 +109,12 @@ namespace MediaBrowser.Controller.Session public string DeviceName { get; set; } /// <summary> + /// Gets or sets the type of the device. + /// </summary> + /// <value>The type of the device.</value> + public string DeviceType { get; set; } + + /// <summary> /// Gets or sets the now playing item. /// </summary> /// <value>The now playing item.</value> @@ -215,8 +221,17 @@ namespace MediaBrowser.Controller.Session public string PlaylistItemId { get; set; } + public string ServerId { get; set; } + public string UserPrimaryImageTag { get; set; } + /// <summary> + /// Gets or sets the supported commands. + /// </summary> + /// <value>The supported commands.</value> + public string[] SupportedCommands + => Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands; + public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory) { var controllers = SessionControllers.ToList(); diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index e11ceb826..4ac249840 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -81,7 +81,7 @@ namespace MediaBrowser.LocalMetadata.Parsers var id = info.Key + "Id"; if (!_validProviderIds.ContainsKey(id)) { - _validProviderIds.Add(id, info.Key); + _validProviderIds.Add(id, info.Key!); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 29043676b..a1a7f55c3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -21,6 +21,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; using System.Diagnostics; +using Microsoft.Extensions.Configuration; namespace MediaBrowser.MediaEncoding.Encoder { @@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly object _runningProcessesLock = new object(); private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>(); - private string _ffmpegPath; + private string _ffmpegPath = string.Empty; private string _ffprobePath; public MediaEncoder( @@ -55,14 +56,14 @@ namespace MediaBrowser.MediaEncoding.Encoder IFileSystem fileSystem, ILocalizationManager localization, Lazy<EncodingHelper> encodingHelperFactory, - string startupOptionsFFmpegPath) + IConfiguration config) { _logger = logger; _configurationManager = configurationManager; _fileSystem = fileSystem; _localization = localization; _encodingHelperFactory = encodingHelperFactory; - _startupOptionFFmpegPath = startupOptionsFFmpegPath; + _startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty; } private EncodingHelper EncodingHelper => _encodingHelperFactory.Value; diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index b87c8fbab..a0c21a666 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -82,8 +82,6 @@ namespace MediaBrowser.Model.Configuration public bool EnableRemoteAccess { get; set; } - public bool CollectionsUpgraded { get; set; } - /// <summary> /// Gets or sets a value indicating whether [enable case sensitive item ids]. /// </summary> @@ -269,6 +267,9 @@ namespace MediaBrowser.Model.Configuration PathSubstitutions = Array.Empty<PathSubstitution>(); IgnoreVirtualInterfaces = false; EnableSimpleArtistDetection = false; + SkipDeserializationForBasicTypes = true; + + PluginRepositories = new List<RepositoryInfo>(); DisplaySpecialsWithinSeasons = true; EnableExternalContentInSuggestions = true; @@ -282,6 +283,9 @@ namespace MediaBrowser.Model.Configuration EnableHttps = false; EnableDashboardResponseCaching = true; EnableCaseSensitiveItemIds = true; + EnableNormalizedItemByNameIds = true; + DisableLiveTvChannelUserDataName = true; + EnableNewOmdbSupport = true; AutoRunWebApp = true; EnableRemoteAccess = true; diff --git a/MediaBrowser.Model/Providers/ExternalIdInfo.cs b/MediaBrowser.Model/Providers/ExternalIdInfo.cs index f2e6d8ef3..01784554f 100644 --- a/MediaBrowser.Model/Providers/ExternalIdInfo.cs +++ b/MediaBrowser.Model/Providers/ExternalIdInfo.cs @@ -1,26 +1,36 @@ -#nullable disable -#pragma warning disable CS1591 - namespace MediaBrowser.Model.Providers { + /// <summary> + /// Represents the external id information for serialization to the client. + /// </summary> public class ExternalIdInfo { /// <summary> - /// Gets or sets the name. + /// Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc). + /// </summary> + // TODO: This should be renamed to ProviderName + public string? Name { get; set; } + + /// <summary> + /// Gets or sets the unique key for this id. This key should be unique across all providers. /// </summary> - /// <value>The name.</value> - public string Name { get; set; } + // TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique. + public string? Key { get; set; } /// <summary> - /// Gets or sets the key. + /// Gets or sets the specific media type for this id. This is used to distinguish between the different + /// external id types for providers with multiple ids. + /// A null value indicates there is no specific media type associated with the external id, or this is the + /// default id for the external provider so there is no need to specify a type. /// </summary> - /// <value>The key.</value> - public string Key { get; set; } + /// <remarks> + /// This can be used along with the <see cref="Name"/> to localize the external id on the client. + /// </remarks> + public ExternalIdMediaType? Type { get; set; } /// <summary> /// Gets or sets the URL format string. /// </summary> - /// <value>The URL format string.</value> - public string UrlFormatString { get; set; } + public string? UrlFormatString { get; set; } } } diff --git a/MediaBrowser.Model/Providers/ExternalIdMediaType.cs b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs new file mode 100644 index 000000000..5303c8f58 --- /dev/null +++ b/MediaBrowser.Model/Providers/ExternalIdMediaType.cs @@ -0,0 +1,71 @@ +namespace MediaBrowser.Model.Providers +{ + /// <summary> + /// The specific media type of an <see cref="ExternalIdInfo"/>. + /// </summary> + /// <remarks> + /// Client applications may use this as a translation key. + /// </remarks> + public enum ExternalIdMediaType + { + /// <summary> + /// A music album. + /// </summary> + Album = 1, + + /// <summary> + /// The artist of a music album. + /// </summary> + AlbumArtist = 2, + + /// <summary> + /// The artist of a media item. + /// </summary> + Artist = 3, + + /// <summary> + /// A boxed set of media. + /// </summary> + BoxSet = 4, + + /// <summary> + /// A series episode. + /// </summary> + Episode = 5, + + /// <summary> + /// A movie. + /// </summary> + Movie = 6, + + /// <summary> + /// An alternative artist apart from the main artist. + /// </summary> + OtherArtist = 7, + + /// <summary> + /// A person. + /// </summary> + Person = 8, + + /// <summary> + /// A release group. + /// </summary> + ReleaseGroup = 9, + + /// <summary> + /// A single season of a series. + /// </summary> + Season = 10, + + /// <summary> + /// A series. + /// </summary> + Series = 11, + + /// <summary> + /// A music track. + /// </summary> + Track = 12 + } +} diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index a51007a0e..86a182fe5 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.Manager _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray(); _metadataProviders = metadataProviders.ToArray(); - _externalIds = externalIds.OrderBy(i => i.Name).ToArray(); + _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); _savers = metadataSavers.Where(i => { @@ -900,7 +900,7 @@ namespace MediaBrowser.Providers.Manager return new ExternalUrl { - Name = i.Name, + Name = i.ProviderName, Url = string.Format( CultureInfo.InvariantCulture, i.UrlFormatString, @@ -914,8 +914,9 @@ namespace MediaBrowser.Providers.Manager return GetExternalIds(item) .Select(i => new ExternalIdInfo { - Name = i.Name, + Name = i.ProviderName, Key = i.Key, + Type = i.Type, UrlFormatString = i.UrlFormatString }); } diff --git a/MediaBrowser.Providers/Movies/MovieExternalIds.cs b/MediaBrowser.Providers/Movies/MovieExternalIds.cs index 4a0cca35e..14080841c 100644 --- a/MediaBrowser.Providers/Movies/MovieExternalIds.cs +++ b/MediaBrowser.Providers/Movies/MovieExternalIds.cs @@ -6,18 +6,22 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Movies { public class ImdbExternalId : IExternalId { /// <inheritdoc /> - public string Name => "IMDb"; + public string ProviderName => "IMDb"; /// <inheritdoc /> public string Key => MetadataProvider.Imdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> public string UrlFormatString => "https://www.imdb.com/title/{0}"; /// <inheritdoc /> @@ -36,12 +40,15 @@ namespace MediaBrowser.Providers.Movies public class ImdbPersonExternalId : IExternalId { /// <inheritdoc /> - public string Name => "IMDb"; + public string ProviderName => "IMDb"; /// <inheritdoc /> public string Key => MetadataProvider.Imdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// <inheritdoc /> public string UrlFormatString => "https://www.imdb.com/name/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index 30c69dd0e..a1726b996 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -3,18 +3,22 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Music { public class ImvdbId : IExternalId { /// <inheritdoc /> - public string Name => "IMVDb"; + public string ProviderName => "IMVDb"; /// <inheritdoc /> public string Key => "IMVDb"; /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> public string UrlFormatString => null; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs index 770dfe80f..1cc1f0fa1 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ExternalIds.cs @@ -3,18 +3,22 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.AudioDb { public class AudioDbAlbumExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// <inheritdoc /> public string Key => MetadataProvider.AudioDbAlbum.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; /// <inheritdoc /> @@ -24,12 +28,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherAlbumExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheAudioDb Album"; + public string ProviderName => "TheAudioDb"; /// <inheritdoc /> public string Key => MetadataProvider.AudioDbAlbum.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; + + /// <inheritdoc /> public string UrlFormatString => "https://www.theaudiodb.com/album/{0}"; /// <inheritdoc /> @@ -39,12 +46,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbArtistExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheAudioDb"; + public string ProviderName => "TheAudioDb"; /// <inheritdoc /> public string Key => MetadataProvider.AudioDbArtist.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; + + /// <inheritdoc /> public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; /// <inheritdoc /> @@ -54,12 +64,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb public class AudioDbOtherArtistExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheAudioDb Artist"; + public string ProviderName => "TheAudioDb"; /// <inheritdoc /> public string Key => MetadataProvider.AudioDbArtist.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; + + /// <inheritdoc /> public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs index 8857cb618..5600c389c 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ExternalIds.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Plugins.MusicBrainz; namespace MediaBrowser.Providers.Music @@ -10,12 +11,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzReleaseGroupExternalId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz Release Group"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}"; /// <inheritdoc /> @@ -25,12 +29,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumArtistExternalId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz Album Artist"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// <inheritdoc /> @@ -40,12 +47,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzAlbumExternalId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz Album"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzAlbum.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Album; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}"; /// <inheritdoc /> @@ -55,12 +65,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzArtistExternalId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzArtist.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Artist; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// <inheritdoc /> @@ -70,13 +83,16 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzOtherArtistExternalId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz Artist"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzArtist.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}"; /// <inheritdoc /> @@ -86,12 +102,15 @@ namespace MediaBrowser.Providers.Music public class MusicBrainzTrackId : IExternalId { /// <inheritdoc /> - public string Name => "MusicBrainz Track"; + public string ProviderName => "MusicBrainz"; /// <inheritdoc /> public string Key => MetadataProvider.MusicBrainzTrack.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Track; + + /// <inheritdoc /> public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 629f95d53..1f7ec6433 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -1,21 +1,26 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { + /// <summary> + /// External ID for a TMDB box set. + /// </summary> public class TmdbBoxSetExternalId : IExternalId { /// <inheritdoc /> - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// <inheritdoc /> public string Key => MetadataProvider.TmdbCollection.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet; + + /// <inheritdoc /> public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs index 095f57398..9610e4058 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs @@ -1,22 +1,27 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { + /// <summary> + /// External ID for a TMBD movie. + /// </summary> public class TmdbMovieExternalId : IExternalId { /// <inheritdoc /> - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// <inheritdoc /> public string Key => MetadataProvider.Tmdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Movie; + + /// <inheritdoc /> public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs index 2c6cf5db4..de74a7a4c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs @@ -1,20 +1,25 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { + /// <summary> + /// External ID for a TMDB person. + /// </summary> public class TmdbPersonExternalId : IExternalId { /// <inheritdoc /> - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// <inheritdoc /> public string Key => MetadataProvider.Tmdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Person; + + /// <inheritdoc /> public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs index 1da203fd7..6ecc055d7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs @@ -1,20 +1,25 @@ -#pragma warning disable CS1591 - using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// External ID for a TMDB series. + /// </summary> public class TmdbSeriesExternalId : IExternalId { /// <inheritdoc /> - public string Name => TmdbUtils.ProviderName; + public string ProviderName => TmdbUtils.ProviderName; /// <inheritdoc /> public string Key => MetadataProvider.Tmdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Series; + + /// <inheritdoc /> public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}"; /// <inheritdoc /> diff --git a/MediaBrowser.Providers/TV/TvExternalIds.cs b/MediaBrowser.Providers/TV/TvExternalIds.cs index 1d904a84c..a6040edd1 100644 --- a/MediaBrowser.Providers/TV/TvExternalIds.cs +++ b/MediaBrowser.Providers/TV/TvExternalIds.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Plugins.TheTvdb; namespace MediaBrowser.Providers.TV @@ -10,12 +11,15 @@ namespace MediaBrowser.Providers.TV public class Zap2ItExternalId : IExternalId { /// <inheritdoc /> - public string Name => "Zap2It"; + public string ProviderName => "Zap2It"; /// <inheritdoc /> public string Key => MetadataProvider.Zap2It.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}"; /// <inheritdoc /> @@ -25,12 +29,15 @@ namespace MediaBrowser.Providers.TV public class TvdbExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// <inheritdoc /> public string Key => MetadataProvider.Tvdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => null; + + /// <inheritdoc /> public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}"; /// <inheritdoc /> @@ -40,12 +47,15 @@ namespace MediaBrowser.Providers.TV public class TvdbSeasonExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// <inheritdoc /> public string Key => MetadataProvider.Tvdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Season; + + /// <inheritdoc /> public string UrlFormatString => null; /// <inheritdoc /> @@ -55,12 +65,15 @@ namespace MediaBrowser.Providers.TV public class TvdbEpisodeExternalId : IExternalId { /// <inheritdoc /> - public string Name => "TheTVDB"; + public string ProviderName => "TheTVDB"; /// <inheritdoc /> public string Key => MetadataProvider.Tvdb.ToString(); /// <inheritdoc /> + public ExternalIdMediaType? Type => ExternalIdMediaType.Episode; + + /// <inheritdoc /> public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}"; /// <inheritdoc /> @@ -16,8 +16,8 @@ <a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget"> <img alt="Translation Status" src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg"/> </a> -<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1"> -<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"/> +<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=29"> +<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20Server"/> </a> <a href="https://hub.docker.com/r/jellyfin/jellyfin"> <img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/> diff --git a/RSSDP/SsdpCommunicationsServer.cs b/RSSDP/SsdpCommunicationsServer.cs index 8fde700e0..a4be32e7d 100644 --- a/RSSDP/SsdpCommunicationsServer.cs +++ b/RSSDP/SsdpCommunicationsServer.cs @@ -339,7 +339,6 @@ namespace Rssdp.Infrastructure private ISocket ListenForBroadcastsAsync() { var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort); - _ = ListenToSocketInternal(socket); return socket; diff --git a/deployment/Dockerfile.docker.amd64 b/deployment/Dockerfile.docker.amd64 new file mode 100644 index 000000000..204ded3a4 --- /dev/null +++ b/deployment/Dockerfile.docker.amd64 @@ -0,0 +1,15 @@ +ARG DOTNET_VERSION=3.1 + +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster + +ARG SOURCE_DIR=/src +ARG ARTIFACT_DIR=/jellyfin + +WORKDIR ${SOURCE_DIR} +COPY . . + +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# because of changes in docker and systemd we need to not build in parallel at the moment +# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" diff --git a/deployment/Dockerfile.docker.arm64 b/deployment/Dockerfile.docker.arm64 new file mode 100644 index 000000000..eedbaac33 --- /dev/null +++ b/deployment/Dockerfile.docker.arm64 @@ -0,0 +1,15 @@ +ARG DOTNET_VERSION=3.1 + +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster + +ARG SOURCE_DIR=/src +ARG ARTIFACT_DIR=/jellyfin + +WORKDIR ${SOURCE_DIR} +COPY . . + +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# because of changes in docker and systemd we need to not build in parallel at the moment +# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" diff --git a/deployment/Dockerfile.docker.armhf b/deployment/Dockerfile.docker.armhf new file mode 100644 index 000000000..2a500246b --- /dev/null +++ b/deployment/Dockerfile.docker.armhf @@ -0,0 +1,15 @@ +ARG DOTNET_VERSION=3.1 + +FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster + +ARG SOURCE_DIR=/src +ARG ARTIFACT_DIR=/jellyfin + +WORKDIR ${SOURCE_DIR} +COPY . . + +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# because of changes in docker and systemd we need to not build in parallel at the moment +# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting +RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none" diff --git a/deployment/build.centos.amd64 b/deployment/build.centos.amd64 index 939bbc45a..69f0cadcf 100755 --- a/deployment/build.centos.amd64 +++ b/deployment/build.centos.amd64 @@ -8,6 +8,22 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd fedora + + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin.spec + sed -i "/%changelog/q" jellyfin.spec + + cat <<EOF >>jellyfin.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org> +- Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} +EOF + popd +fi + # Build RPM make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS rpmbuild --rebuild -bb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm diff --git a/deployment/build.debian.amd64 b/deployment/build.debian.amd64 index f44c6a7d1..012e1cebf 100755 --- a/deployment/build.debian.amd64 +++ b/deployment/build.debian.amd64 @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB dpkg-buildpackage -us -uc --pre-clean --post-clean diff --git a/deployment/build.debian.arm64 b/deployment/build.debian.arm64 index 0127671f3..12ce3e874 100755 --- a/deployment/build.debian.arm64 +++ b/deployment/build.debian.arm64 @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean diff --git a/deployment/build.debian.armhf b/deployment/build.debian.armhf index 02e3db4fc..3089eab58 100755 --- a/deployment/build.debian.armhf +++ b/deployment/build.debian.armhf @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean diff --git a/deployment/build.fedora.amd64 b/deployment/build.fedora.amd64 index 8ac99decc..2c7bff506 100755 --- a/deployment/build.fedora.amd64 +++ b/deployment/build.fedora.amd64 @@ -8,6 +8,22 @@ set -o xtrace # Move to source directory pushd ${SOURCE_DIR} +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd fedora + + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + sed -i "s/Version:.*/Version: ${BUILD_ID}/" jellyfin.spec + sed -i "/%changelog/q" jellyfin.spec + + cat <<EOF >>jellyfin.spec +* $( LANG=C date '+%a %b %d %Y' ) Jellyfin Packaging Team <packaging@jellyfin.org> +- Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} +EOF + popd +fi + # Build RPM make -f fedora/Makefile srpm outdir=/root/rpmbuild/SRPMS rpmbuild -rb /root/rpmbuild/SRPMS/jellyfin-*.src.rpm diff --git a/deployment/build.linux.amd64 b/deployment/build.linux.amd64 index 0cbbd05cf..a7fb0544a 100755 --- a/deployment/build.linux.amd64 +++ b/deployment/build.linux.amd64 @@ -9,7 +9,11 @@ set -o xtrace pushd ${SOURCE_DIR} # Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi # Build archives dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" diff --git a/deployment/build.macos b/deployment/build.macos index 16be29eee..d808141ac 100755 --- a/deployment/build.macos +++ b/deployment/build.macos @@ -9,7 +9,11 @@ set -o xtrace pushd ${SOURCE_DIR} # Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi # Build archives dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" diff --git a/deployment/build.portable b/deployment/build.portable index 1e8a4ab62..24a8cbf32 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -9,7 +9,11 @@ set -o xtrace pushd ${SOURCE_DIR} # Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi # Build archives dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none;UseAppHost=true" diff --git a/deployment/build.ubuntu.amd64 b/deployment/build.ubuntu.amd64 index 107ddbe02..0eac9cdd1 100755 --- a/deployment/build.ubuntu.amd64 +++ b/deployment/build.ubuntu.amd64 @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB dpkg-buildpackage -us -uc --pre-clean --post-clean diff --git a/deployment/build.ubuntu.arm64 b/deployment/build.ubuntu.arm64 index b13868f44..5b11fd543 100755 --- a/deployment/build.ubuntu.arm64 +++ b/deployment/build.ubuntu.arm64 @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a arm64 --pre-clean --post-clean diff --git a/deployment/build.ubuntu.armhf b/deployment/build.ubuntu.armhf index 0b4dd308a..4734cf658 100755 --- a/deployment/build.ubuntu.armhf +++ b/deployment/build.ubuntu.armhf @@ -14,6 +14,21 @@ if [[ ${IS_DOCKER} == YES ]]; then sed -i '/dotnet-sdk-3.1,/d' debian/control fi +# Modify changelog to unstable configuration if IS_UNSTABLE +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + pushd debian + PR_ID=$( git log --grep 'Merge pull request' --oneline --single-worktree --first-parent | head -1 | grep --color=none -Eo '#[0-9]+' | tr -d '#' ) + + cat <<EOF >changelog +jellyfin-server (${BUILD_ID}-unstable) unstable; urgency=medium + + * Jellyfin Server unstable build ${BUILD_ID} for merged PR #${PR_ID} + + -- Jellyfin Packaging Team <packaging@jellyfin.org> $( date --rfc-2822 ) +EOF + popd +fi + # Build DEB export CONFIG_SITE=/etc/dpkg-cross/cross-config.${ARCH} dpkg-buildpackage -us -uc -a armhf --pre-clean --post-clean diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64 index 39bd41f99..3fabc2cac 100755 --- a/deployment/build.windows.amd64 +++ b/deployment/build.windows.amd64 @@ -15,7 +15,11 @@ FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip pushd ${SOURCE_DIR} # Get version -version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +if [[ ${IS_UNSTABLE} == 'yes' ]]; then + version="${BUILD_ID}" +else + version="$( grep "version:" ./build.yaml | sed -E 's/version: "([0-9\.]+.*)"/\1/' )" +fi output_dir="dist/jellyfin-server_${version}" diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 074bf2371..100f95424 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -13,15 +13,15 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="AutoFixture" Version="4.11.0" /> - <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" /> - <PackageReference Include="AutoFixture.Xunit2" Version="4.11.0" /> + <PackageReference Include="AutoFixture" Version="4.12.0" /> + <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" /> + <PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" /> <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="1.3.0" /> - <PackageReference Include="Moq" Version="4.14.3" /> + <PackageReference Include="Moq" Version="4.14.4" /> </ItemGroup> <!-- Code Analyzers --> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 91fd0e2e2..dd53c9902 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -14,9 +14,9 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="AutoFixture" Version="4.11.0" /> - <PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" /> - <PackageReference Include="Moq" Version="4.14.3" /> + <PackageReference Include="AutoFixture" Version="4.12.0" /> + <PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" /> + <PackageReference Include="Moq" Version="4.14.4" /> <PackageReference Include="xunit" Version="2.4.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> <PackageReference Include="coverlet.collector" Version="1.3.0" /> diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs index 26dee38c6..c145ddc9d 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/Library/IgnorePatternsTests.cs @@ -7,12 +7,19 @@ namespace Jellyfin.Server.Implementations.Tests.Library { [Theory] [InlineData("/media/small.jpg", true)] + [InlineData("/media/albumart.jpg", true)] + [InlineData("/media/movie.sample.mp4", true)] [InlineData("/media/movies/#Recycle/test.txt", true)] [InlineData("/media/movies/#recycle/", true)] + [InlineData("/media/movies/#recycle", true)] [InlineData("thumbs.db", true)] [InlineData(@"C:\media\movies\movie.avi", false)] [InlineData("/media/.hiddendir/file.mp4", true)] [InlineData("/media/dir/.hiddenfile.mp4", true)] + [InlineData("/volume1/video/Series/@eaDir", true)] + [InlineData("/volume1/video/Series/@eaDir/file.txt", true)] + [InlineData("/directory/@Recycle", true)] + [InlineData("/directory/@Recycle/file.mp3", true)] public void PathIgnored(string path, bool expected) { Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path)); |
