mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-16 02:18:54 -07:00
Merge branch 'master' into feature/ffmpeg-version-check
This commit is contained in:
commit
3588ee5229
131
.ci/azure-pipelines-package.yml
Normal file
131
.ci/azure-pipelines-package.yml
Normal file
@ -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)'
|
@ -43,3 +43,5 @@ jobs:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
LinuxImage: 'ubuntu-latest'
|
||||
|
||||
- template: azure-pipelines-package.yml
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -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"
|
||||
}
|
||||
}
|
||||
|
7
.vscode/tasks.json
vendored
7
.vscode/tasks.json
vendored
@ -21,5 +21,10 @@
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
],
|
||||
"options": {
|
||||
"env": {
|
||||
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>));
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
@ -84,6 +48,81 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
SetRangeValues(contentLength);
|
||||
}
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional HTTP Headers
|
||||
/// </summary>
|
||||
/// <value>The headers.</value>
|
||||
public IDictionary<string, string> Headers => _options;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
@ -115,50 +154,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _requested ranges.
|
||||
/// </summary>
|
||||
private List<KeyValuePair<long, long?>> _requestedRanges;
|
||||
/// <summary>
|
||||
/// Gets the requested ranges.
|
||||
/// </summary>
|
||||
/// <value>The requested ranges.</value>
|
||||
protected List<KeyValuePair<long, long?>> RequestedRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_requestedRanges == null)
|
||||
{
|
||||
_requestedRanges = new List<KeyValuePair<long, long?>>();
|
||||
|
||||
// Example: bytes=0-,32-63
|
||||
var ranges = RangeHeader.Split('=')[1].Split(',');
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var vals = range.Split('-');
|
||||
|
||||
long start = 0;
|
||||
long? end = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[0]))
|
||||
{
|
||||
start = long.Parse(vals[0], UsCulture);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vals[1]))
|
||||
{
|
||||
end = long.Parse(vals[1], UsCulture);
|
||||
}
|
||||
|
||||
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
return _requestedRanges;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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": "تم تثبيت الملحق",
|
||||
|
@ -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",
|
||||
|
@ -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}",
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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": "भिडियो प्लेब्याक सुरु भयो"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -36,7 +36,6 @@ namespace Jellyfin.Api.Controllers
|
||||
public void CompleteWizard()
|
||||
{
|
||||
_config.Configuration.IsStartupWizardCompleted = true;
|
||||
_config.SetOptimalValues();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
|
@ -101,6 +101,11 @@ namespace Jellyfin.Server
|
||||
config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString());
|
||||
}
|
||||
|
||||
if (FFmpegPath != null)
|
||||
{
|
||||
config.Add(ConfigurationExtensions.FfmpegPathKey, FFmpegPath);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,5 @@ namespace MediaBrowser.Controller.Configuration
|
||||
/// </summary>
|
||||
/// <value>The configuration.</value>
|
||||
ServerConfiguration Configuration { get; }
|
||||
|
||||
bool SetOptimalValues();
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -23,6 +23,11 @@ namespace MediaBrowser.Controller.Extensions
|
||||
/// </summary>
|
||||
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>
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,12 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <value>The name of the device.</value>
|
||||
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>
|
||||
@ -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();
|
||||
|
@ -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!);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
// TODO: This should be renamed to ProviderName
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key.
|
||||
/// Gets or sets the unique key for this id. This key should be unique across all providers.
|
||||
/// </summary>
|
||||
/// <value>The key.</value>
|
||||
public string Key { 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 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="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; }
|
||||
}
|
||||
}
|
||||
|
71
MediaBrowser.Model/Providers/ExternalIdMediaType.cs
Normal file
71
MediaBrowser.Model/Providers/ExternalIdMediaType.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
|
@ -6,17 +6,21 @@ 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}";
|
||||
|
||||
@ -36,11 +40,14 @@ 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}";
|
||||
|
||||
|
@ -3,17 +3,21 @@
|
||||
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;
|
||||
|
||||
|
@ -3,17 +3,21 @@
|
||||
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}";
|
||||
|
||||
@ -24,11 +28,14 @@ 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}";
|
||||
|
||||
@ -39,11 +46,14 @@ 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}";
|
||||
|
||||
@ -54,11 +64,14 @@ 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}";
|
||||
|
||||
|
@ -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,11 +11,14 @@ 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}";
|
||||
|
||||
@ -25,11 +29,14 @@ 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}";
|
||||
|
||||
@ -40,11 +47,14 @@ 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}";
|
||||
|
||||
@ -55,11 +65,14 @@ 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}";
|
||||
|
||||
@ -70,12 +83,15 @@ 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}";
|
||||
|
||||
@ -86,11 +102,14 @@ 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}";
|
||||
|
||||
|
@ -1,20 +1,25 @@
|
||||
#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}";
|
||||
|
||||
|
@ -1,21 +1,26 @@
|
||||
#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}";
|
||||
|
||||
|
@ -1,19 +1,24 @@
|
||||
#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}";
|
||||
|
||||
|
@ -1,19 +1,24 @@
|
||||
#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}";
|
||||
|
||||
|
@ -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,11 +11,14 @@ 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}";
|
||||
|
||||
@ -25,11 +29,14 @@ 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}";
|
||||
|
||||
@ -40,11 +47,14 @@ 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;
|
||||
|
||||
@ -55,11 +65,14 @@ 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}";
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -339,7 +339,6 @@ namespace Rssdp.Infrastructure
|
||||
private ISocket ListenForBroadcastsAsync()
|
||||
{
|
||||
var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort);
|
||||
|
||||
_ = ListenToSocketInternal(socket);
|
||||
|
||||
return socket;
|
||||
|
15
deployment/Dockerfile.docker.amd64
Normal file
15
deployment/Dockerfile.docker.amd64
Normal file
@ -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"
|
15
deployment/Dockerfile.docker.arm64
Normal file
15
deployment/Dockerfile.docker.arm64
Normal file
@ -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"
|
15
deployment/Dockerfile.docker.armhf
Normal file
15
deployment/Dockerfile.docker.armhf
Normal file
@ -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"
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}"
|
||||
|
||||
|
@ -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 -->
|
||||
|
@ -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" />
|
||||
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user