aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs3
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs9
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs32
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs15
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs21
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs47
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionPathsTask.cs119
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs9
-rw-r--r--Emby.Server.Implementations/Session/WebSocketController.cs4
10 files changed, 187 insertions, 76 deletions
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 179683055..b34d0f21e 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -112,7 +112,8 @@ namespace Emby.Server.Implementations.Collections
return Path.Combine(_appPaths.DataPath, "collections");
}
- private Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
+ /// <inheritdoc />
+ public Task<Folder?> GetCollectionsFolder(bool createIfNeeded)
{
return EnsureLibraryFolder(GetCollectionsFolderPath(), createIfNeeded);
}
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index ca8f605a0..73ec856fc 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -2452,7 +2452,9 @@ namespace Emby.Server.Implementations.Data
if (query.SearchTerm.Length > 1)
{
builder.Append("+ ((CleanName like @SearchTermContains or (OriginalTitle not null and OriginalTitle like @SearchTermContains)) * 10)");
- builder.Append("+ ((Tags not null and Tags like @SearchTermContains) * 5)");
+ builder.Append("+ (SELECT COUNT(1) * 1 from ItemValues where ItemId=Guid and CleanValue like @SearchTermContains)");
+ builder.Append("+ (SELECT COUNT(1) * 2 from ItemValues where ItemId=Guid and CleanValue like @SearchTermStartsWith)");
+ builder.Append("+ (SELECT COUNT(1) * 10 from ItemValues where ItemId=Guid and CleanValue like @SearchTermEquals)");
}
builder.Append(") as SearchScore");
@@ -2483,6 +2485,11 @@ namespace Emby.Server.Implementations.Data
{
statement.TryBind("@SearchTermContains", "%" + searchTerm + "%");
}
+
+ if (commandText.Contains("@SearchTermEquals", StringComparison.OrdinalIgnoreCase))
+ {
+ statement.TryBind("@SearchTermEquals", searchTerm);
+ }
}
private void BindSimilarParams(InternalItemsQuery query, IStatement statement)
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index b1a99853a..fd7653a32 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -9,7 +9,8 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages;
+using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -85,14 +86,15 @@ namespace Emby.Server.Implementations.HttpServer
/// <value>The state.</value>
public WebSocketState State => _socket.State;
- /// <summary>
- /// Sends a message asynchronously.
- /// </summary>
- /// <typeparam name="T">The type of the message.</typeparam>
- /// <param name="message">The message.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task SendAsync<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task SendAsync(OutboundWebSocketMessage message, CancellationToken cancellationToken)
+ {
+ var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions);
+ return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken);
+ }
+
+ /// <inheritdoc />
+ public Task SendAsync<T>(OutboundWebSocketMessage<T> message, CancellationToken cancellationToken)
{
var json = JsonSerializer.SerializeToUtf8Bytes(message, _jsonOptions);
return _socket.SendAsync(json, WebSocketMessageType.Text, true, cancellationToken);
@@ -171,7 +173,7 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- WebSocketMessage<object>? stub;
+ InboundWebSocketMessage<object>? stub;
long bytesConsumed;
try
{
@@ -212,10 +214,10 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- internal WebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed)
+ internal InboundWebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed)
{
var jsonReader = new Utf8JsonReader(bytes);
- var ret = JsonSerializer.Deserialize<WebSocketMessage<object>>(ref jsonReader, _jsonOptions);
+ var ret = JsonSerializer.Deserialize<InboundWebSocketMessage<object>>(ref jsonReader, _jsonOptions);
bytesConsumed = jsonReader.BytesConsumed;
return ret;
}
@@ -224,11 +226,7 @@ namespace Emby.Server.Implementations.HttpServer
{
LastKeepAliveDate = DateTime.UtcNow;
return SendAsync(
- new WebSocketMessage<string>
- {
- MessageId = Guid.NewGuid(),
- MessageType = SessionMessageType.KeepAlive
- },
+ new OutboundKeepAliveMessage(),
CancellationToken.None);
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 1fffdfbfa..0ba4a488b 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -20,6 +20,14 @@ namespace Emby.Server.Implementations.IO
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
private readonly string _tempPath;
private static readonly bool _isEnvironmentCaseInsensitive = OperatingSystem.IsWindows();
+ private static readonly char[] _invalidPathCharacters =
+ {
+ '\"', '<', '>', '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31, ':', '*', '?', '\\', '/'
+ };
/// <summary>
/// Initializes a new instance of the <see cref="ManagedFileSystem"/> class.
@@ -275,8 +283,7 @@ namespace Emby.Server.Implementations.IO
/// <exception cref="ArgumentNullException">The filename is null.</exception>
public string GetValidFilename(string filename)
{
- var invalid = Path.GetInvalidFileNameChars();
- var first = filename.IndexOfAny(invalid);
+ var first = filename.IndexOfAny(_invalidPathCharacters);
if (first == -1)
{
// Fast path for clean strings
@@ -285,7 +292,7 @@ namespace Emby.Server.Implementations.IO
return string.Create(
filename.Length,
- (filename, invalid, first),
+ (filename, _invalidPathCharacters, first),
(chars, state) =>
{
state.filename.AsSpan().CopyTo(chars);
@@ -293,7 +300,7 @@ namespace Emby.Server.Implementations.IO
chars[state.first++] = ' ';
var len = chars.Length;
- foreach (var c in state.invalid)
+ foreach (var c in state._invalidPathCharacters)
{
for (int i = state.first; i < len; i++)
{
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index ea45bf0ba..8bb2d3c02 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2069,7 +2069,9 @@ namespace Emby.Server.Implementations.Library
.Find(folder => folder is CollectionFolder) as CollectionFolder;
}
- return collectionFolder is null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
+ return collectionFolder is null
+ ? new LibraryOptions()
+ : collectionFolder.GetLibraryOptions();
}
public string GetContentType(BaseItem item)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index bcb42e162..acf3964c8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -30,12 +30,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
- private static readonly string[] _disallowedSharedStreamExtensions =
+ private static readonly string[] _disallowedMimeTypes =
{
- ".mkv",
- ".mp4",
- ".m3u8",
- ".mpd"
+ "video/x-matroska",
+ "video/mp4",
+ "application/vnd.apple.mpegurl",
+ "application/mpegurl",
+ "application/x-mpegurl",
+ "video/vnd.mpeg.dash.mpd"
};
private readonly IHttpClientFactory _httpClientFactory;
@@ -118,9 +120,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
{
- var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty;
+ using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(message, cancellationToken)
+ .ConfigureAwait(false);
- if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
+ response.EnsureSuccessStatusCode();
+
+ if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index e84e1e074..51f46f4da 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -38,7 +38,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_httpClientFactory = httpClientFactory;
_appHost = appHost;
OriginalStreamId = originalStreamId;
- EnableStreamSharing = true;
}
public override async Task Open(CancellationToken openCancellationToken)
@@ -59,39 +58,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
.ConfigureAwait(false);
- var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty;
- if (contentType.Contains("matroska", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("mp4", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("dash", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("mpegURL", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("text/", StringComparison.OrdinalIgnoreCase))
- {
- // Close the stream without any sharing features
- response.Dispose();
- return;
- }
-
- SetTempFilePath("ts");
-
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
- // OpenedMediaSource.Protocol = MediaProtocol.File;
- // OpenedMediaSource.Path = tempFile;
- // OpenedMediaSource.ReadAtNativeFramerate = true;
-
MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
- // OpenedMediaSource.Path = TempFilePath;
- // OpenedMediaSource.Protocol = MediaProtocol.File;
-
- // OpenedMediaSource.Path = _tempFilePath;
- // OpenedMediaSource.Protocol = MediaProtocol.File;
- // OpenedMediaSource.SupportsDirectPlay = false;
- // OpenedMediaSource.SupportsDirectStream = true;
- // OpenedMediaSource.SupportsTranscoding = true;
var res = await taskCompletionSource.Task.ConfigureAwait(false);
if (!res)
{
@@ -108,15 +81,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try
{
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
- using var message = response;
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
- await StreamHelper.CopyToAsync(
- stream,
- fileStream,
- IODefaults.CopyToBufferSize,
- () => Resolve(openTaskCompletionSource),
- cancellationToken).ConfigureAwait(false);
+ using (response)
+ {
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ IODefaults.CopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
+ }
}
catch (OperationCanceledException ex)
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionPathsTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionPathsTask.cs
new file mode 100644
index 000000000..f78fc6f97
--- /dev/null
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionPathsTask.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Collections;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// Deletes Path references from collections that no longer exists.
+/// </summary>
+public class CleanupCollectionPathsTask : IScheduledTask
+{
+ private readonly ILocalizationManager _localization;
+ private readonly ICollectionManager _collectionManager;
+ private readonly ILogger<CleanupCollectionPathsTask> _logger;
+ private readonly IProviderManager _providerManager;
+ private readonly IFileSystem _fileSystem;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CleanupCollectionPathsTask"/> class.
+ /// </summary>
+ /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ /// <param name="collectionManager">Instance of the <see cref="ICollectionManager"/> interface.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="providerManager">The provider manager.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ public CleanupCollectionPathsTask(
+ ILocalizationManager localization,
+ ICollectionManager collectionManager,
+ ILogger<CleanupCollectionPathsTask> logger,
+ IProviderManager providerManager,
+ IFileSystem fileSystem)
+ {
+ _localization = localization;
+ _collectionManager = collectionManager;
+ _logger = logger;
+ _providerManager = providerManager;
+ _fileSystem = fileSystem;
+ }
+
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TaskCleanCollections");
+
+ /// <inheritdoc />
+ public string Key => "CleanCollections";
+
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TaskCleanCollectionsDescription");
+
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory");
+
+ /// <inheritdoc />
+ public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var collectionsFolder = await _collectionManager.GetCollectionsFolder(false).ConfigureAwait(false);
+ if (collectionsFolder is null)
+ {
+ _logger.LogDebug("There is no collection folder to be found");
+ return;
+ }
+
+ var collections = collectionsFolder.Children.OfType<BoxSet>().ToArray();
+ _logger.LogDebug("Found {CollectionLength} Boxsets", collections.Length);
+
+ var itemsToRemove = new List<LinkedChild>();
+ for (var index = 0; index < collections.Length; index++)
+ {
+ var collection = collections[index];
+ _logger.LogDebug("Check Boxset {CollectionName}", collection.Name);
+
+ foreach (var collectionLinkedChild in collection.LinkedChildren)
+ {
+ if (!File.Exists(collectionLinkedChild.Path))
+ {
+ _logger.LogInformation("Item in boxset {CollectionName} cannot be found at {ItemPath}", collection.Name, collectionLinkedChild.Path);
+ itemsToRemove.Add(collectionLinkedChild);
+ }
+ }
+
+ if (itemsToRemove.Count != 0)
+ {
+ _logger.LogDebug("Update Boxset {CollectionName}", collection.Name);
+ collection.LinkedChildren = collection.LinkedChildren.Except(itemsToRemove).ToArray();
+ await collection.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken)
+ .ConfigureAwait(false);
+
+ _providerManager.QueueRefresh(
+ collection.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ ForceSave = true
+ },
+ RefreshPriority.High);
+
+ itemsToRemove.Clear();
+ }
+
+ progress.Report(100D / collections.Length * (index + 1));
+ }
+ }
+
+ /// <inheritdoc />
+ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+ {
+ return new[] { new TaskTriggerInfo() { Type = TaskTriggerInfo.TriggerStartup } };
+ }
+}
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index 4e427b1a4..b3c93a904 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -6,9 +6,8 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Net;
-using MediaBrowser.Model.Session;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -308,11 +307,7 @@ namespace Emby.Server.Implementations.Session
private Task SendForceKeepAlive(IWebSocketConnection webSocket)
{
return webSocket.SendAsync(
- new WebSocketMessage<int>
- {
- MessageType = SessionMessageType.ForceKeepAlive,
- Data = WebSocketLostTimeout
- },
+ new ForceKeepAliveMessage(WebSocketLostTimeout),
CancellationToken.None);
}
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index cdc736950..cf8e0fb00 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -7,8 +7,8 @@ using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Net.WebSocketMessages;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Session
}
return socket.SendAsync(
- new WebSocketMessage<T>
+ new OutboundWebSocketMessage<T>
{
Data = data,
MessageType = name,