Merge branch 'master' into feature/ffmpeg-version-check

pull/3216/head
Max Git 4 years ago
commit 3588ee5229

@ -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

@ -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

@ -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"
}
}

@ -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;
@ -85,40 +49,26 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
/// Sets the range values.
/// Gets or sets the source stream.
/// </summary>
private void SetRangeValues(long contentLength)
{
var requestedRange = RequestedRanges[0];
TotalContentLength = contentLength;
// If the requested range is "0-", we can optimize by just doing a stream copy
if (!requestedRange.Value.HasValue)
{
RangeEnd = TotalContentLength - 1;
}
else
{
RangeEnd = requestedRange.Value.Value;
}
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
/// <value>The source stream.</value>
private Stream SourceStream { get; set; }
private string RangeHeader { get; set; }
private bool IsHeadRequest { get; set; }
Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture);
Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
private long RangeStart { get; set; }
private long RangeEnd { get; set; }
private long RangeLength { get; set; }
private long TotalContentLength { get; set; }
if (RangeStart > 0 && SourceStream.CanSeek)
{
SourceStream.Position = RangeStart;
}
}
public Action OnComplete { get; set; }
/// <summary>
/// The _requested ranges.
/// Additional HTTP Headers
/// </summary>
private List<KeyValuePair<long, long?>> _requestedRanges;
/// <value>The headers.</value>
public IDictionary<string, string> Headers => _options;
/// <summary>
/// Gets the requested ranges.
/// </summary>
@ -143,12 +93,12 @@ namespace Emby.Server.Implementations.HttpServer
if (!string.IsNullOrEmpty(vals[0]))
{
start = long.Parse(vals[0], UsCulture);
start = long.Parse(vals[0], CultureInfo.InvariantCulture);
}
if (!string.IsNullOrEmpty(vals[1]))
{
end = long.Parse(vals[1], UsCulture);
end = long.Parse(vals[1], CultureInfo.InvariantCulture);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
@ -159,6 +109,51 @@ namespace Emby.Server.Implementations.HttpServer
}
}
public string ContentType { get; set; }
public IRequest RequestContext { get; set; }
public object Response { get; set; }
public int Status { get; set; }
public HttpStatusCode StatusCode
{
get => (HttpStatusCode)Status;
set => Status = (int)value;
}
/// <summary>
/// Sets the range values.
/// </summary>
private void SetRangeValues(long contentLength)
{
var requestedRange = RequestedRanges[0];
TotalContentLength = contentLength;
// If the requested range is "0-", we can optimize by just doing a stream copy
if (!requestedRange.Value.HasValue)
{
RangeEnd = TotalContentLength - 1;
}
else
{
RangeEnd = requestedRange.Value.Value;
}
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
Headers[HeaderNames.ContentLength] = RangeLength.ToString(CultureInfo.InvariantCulture);
Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
if (RangeStart > 0 && SourceStream.CanSeek)
{
SourceStream.Position = RangeStart;
}
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
{
try
@ -174,59 +169,44 @@ namespace Emby.Server.Implementations.HttpServer
// If the requested range is "0-", we can optimize by just doing a stream copy
if (RangeEnd >= TotalContentLength - 1)
{
await source.CopyToAsync(responseStream, BufferSize).ConfigureAwait(false);
await source.CopyToAsync(responseStream, BufferSize, cancellationToken).ConfigureAwait(false);
}
else
{
await CopyToInternalAsync(source, responseStream, RangeLength).ConfigureAwait(false);
await CopyToInternalAsync(source, responseStream, RangeLength, cancellationToken).ConfigureAwait(false);
}
}
}
finally
{
if (OnComplete != null)
{
OnComplete();
}
OnComplete?.Invoke();
}
}
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength)
private static async Task CopyToInternalAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken)
{
var array = new byte[BufferSize];
int bytesRead;
while ((bytesRead = await source.ReadAsync(array, 0, array.Length).ConfigureAwait(false)) != 0)
var array = ArrayPool<byte>.Shared.Rent(BufferSize);
try
{
if (bytesRead == 0)
int bytesRead;
while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0)
{
break;
}
var bytesToCopy = Math.Min(bytesRead, copyLength);
var bytesToCopy = Math.Min(bytesRead, copyLength);
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy), cancellationToken).ConfigureAwait(false);
await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToCopy)).ConfigureAwait(false);
copyLength -= bytesToCopy;
copyLength -= bytesToCopy;
if (copyLength <= 0)
{
break;
if (copyLength <= 0)
{
break;
}
}
}
}
public string ContentType { get; set; }
public IRequest RequestContext { get; set; }
public object Response { get; set; }
public int Status { get; set; }
public HttpStatusCode StatusCode
{
get => (HttpStatusCode)Status;
set => Status = (int)value;
finally
{
ArrayPool<byte>.Shared.Return(array);
}
}
}
}

@ -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>
// TODO: This should be renamed to ProviderName
public string? Name { get; set; }
/// <summary>
/// Gets or sets the unique key for this id. This key should be unique across all providers.
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
// TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique.
public string? Key { get; set; }
/// <summary>
/// Gets or sets the key.
/// Gets or sets the specific media type for this id. This is used to distinguish between the different
/// external id types for providers with multiple ids.
/// A null value indicates there is no specific media type associated with the external id, or this is the
/// default id for the external provider so there is no need to specify a type.
/// </summary>
/// <value>The key.</value>
public string Key { get; set; }
/// <remarks>
/// This can be used along with the <see cref="Name"/> to localize the external id on the client.
/// </remarks>
public ExternalIdMediaType? Type { get; set; }
/// <summary>
/// Gets or sets the URL format string.
/// </summary>
/// <value>The URL format string.</value>
public string UrlFormatString { get; set; }
public string? UrlFormatString { get; set; }
}
}

@ -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;

@ -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"

@ -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"

@ -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…
Cancel
Save