mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 09:04:42 +01:00
Merge remote-tracking branch 'upstream/master' into api-migration
This commit is contained in:
@@ -191,7 +191,7 @@ namespace Emby.Server.Implementations
|
||||
/// Gets or sets the application paths.
|
||||
/// </summary>
|
||||
/// <value>The application paths.</value>
|
||||
protected ServerApplicationPaths ApplicationPaths { get; set; }
|
||||
protected IServerApplicationPaths ApplicationPaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets all concrete types.
|
||||
@@ -235,7 +235,7 @@ namespace Emby.Server.Implementations
|
||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||
/// </summary>
|
||||
public ApplicationHost(
|
||||
ServerApplicationPaths applicationPaths,
|
||||
IServerApplicationPaths applicationPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IStartupOptions options,
|
||||
IFileSystem fileSystem,
|
||||
@@ -553,8 +553,6 @@ namespace Emby.Server.Implementations
|
||||
serviceCollection.AddSingleton<IUserDataRepository, SqliteUserDataRepository>();
|
||||
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
|
||||
|
||||
serviceCollection.AddSingleton<IDisplayPreferencesRepository, SqliteDisplayPreferencesRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IItemRepository, SqliteItemRepository>();
|
||||
|
||||
serviceCollection.AddSingleton<IAuthenticationRepository, AuthenticationRepository>();
|
||||
@@ -651,7 +649,6 @@ namespace Emby.Server.Implementations
|
||||
_httpServer = Resolve<IHttpServer>();
|
||||
_httpClient = Resolve<IHttpClient>();
|
||||
|
||||
((SqliteDisplayPreferencesRepository)Resolve<IDisplayPreferencesRepository>()).Initialize();
|
||||
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
|
||||
|
||||
SetStaticProperties();
|
||||
@@ -796,7 +793,6 @@ namespace Emby.Server.Implementations
|
||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||
|
||||
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
||||
Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
|
||||
|
||||
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -7,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
@@ -22,6 +22,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
|
||||
@@ -45,10 +46,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IProviderManager _providerManager;
|
||||
|
||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||
|
||||
/// <summary>
|
||||
@@ -63,6 +61,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
/// <param name="userDataManager">The user data manager.</param>
|
||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||
/// <param name="providerManager">The provider manager.</param>
|
||||
/// <param name="memoryCache">The memory cache.</param>
|
||||
public ChannelManager(
|
||||
IUserManager userManager,
|
||||
IDtoService dtoService,
|
||||
@@ -72,7 +71,8 @@ namespace Emby.Server.Implementations.Channels
|
||||
IFileSystem fileSystem,
|
||||
IUserDataManager userDataManager,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IProviderManager providerManager)
|
||||
IProviderManager providerManager,
|
||||
IMemoryCache memoryCache)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_dtoService = dtoService;
|
||||
@@ -83,6 +83,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
_userDataManager = userDataManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_providerManager = providerManager;
|
||||
_memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
internal IChannel[] Channels { get; private set; }
|
||||
@@ -417,20 +418,15 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
||||
{
|
||||
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
||||
if (_memoryCache.TryGetValue(id, out List<MediaSourceInfo> cachedInfo))
|
||||
{
|
||||
if ((DateTime.UtcNow - cachedInfo.Item1).TotalMinutes < 5)
|
||||
{
|
||||
return cachedInfo.Item2;
|
||||
}
|
||||
return cachedInfo;
|
||||
}
|
||||
|
||||
var mediaInfo = await channel.GetChannelItemMediaInfo(id, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var list = mediaInfo.ToList();
|
||||
|
||||
var item2 = new Tuple<DateTime, List<MediaSourceInfo>>(DateTime.UtcNow, list);
|
||||
_channelItemMediaInfo.AddOrUpdate(id, item2, (key, oldValue) => item2);
|
||||
_memoryCache.CreateEntry(id).SetValue(list).SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddMinutes(5));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteDisplayPreferencesRepository.
|
||||
/// </summary>
|
||||
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem)
|
||||
: base(logger)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
_jsonOptions = JsonDefaults.GetOptions();
|
||||
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "SQLite";
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeInternal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
||||
|
||||
_fileSystem.DeleteFile(DbFilePath);
|
||||
|
||||
InitializeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database.
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void InitializeInternal()
|
||||
{
|
||||
string[] queries =
|
||||
{
|
||||
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
|
||||
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
|
||||
};
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunQueries(queries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayPreferences.Id))
|
||||
{
|
||||
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db => SaveDisplayPreferences(displayPreferences, userId, client, db),
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection)
|
||||
{
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions);
|
||||
|
||||
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
|
||||
{
|
||||
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all display preferences associated with a user in the repo.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
foreach (var displayPreference in displayPreferences)
|
||||
{
|
||||
SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
|
||||
}
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display preferences.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferencesId">The display preferences id.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client)
|
||||
{
|
||||
if (string.IsNullOrEmpty(displayPreferencesId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferencesId));
|
||||
}
|
||||
|
||||
var guidId = displayPreferencesId.GetMD5();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
|
||||
{
|
||||
statement.TryBind("@id", guidId.ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return Get(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DisplayPreferences
|
||||
{
|
||||
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all display preferences for the given user.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
|
||||
{
|
||||
var list = new List<DisplayPreferences>();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
|
||||
{
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(Get(row));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
|
||||
=> JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions);
|
||||
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
|
||||
=> SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
|
||||
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
|
||||
=> GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Emby.Server.Implementations.Playlists;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -400,6 +401,8 @@ namespace Emby.Server.Implementations.Data
|
||||
"OwnerId"
|
||||
};
|
||||
|
||||
private static readonly string _retriveItemColumnsSelectQuery = $"select {string.Join(',', _retriveItemColumns)} from TypedBaseItems where guid = @guid";
|
||||
|
||||
private static readonly string[] _mediaStreamSaveColumns =
|
||||
{
|
||||
"ItemId",
|
||||
@@ -439,6 +442,12 @@ namespace Emby.Server.Implementations.Data
|
||||
"ColorTransfer"
|
||||
};
|
||||
|
||||
private static readonly string _mediaStreamSaveColumnsInsertQuery =
|
||||
$"insert into mediastreams ({string.Join(',', _mediaStreamSaveColumns)}) values ";
|
||||
|
||||
private static readonly string _mediaStreamSaveColumnsSelectQuery =
|
||||
$"select {string.Join(',', _mediaStreamSaveColumns)} from mediastreams where ItemId=@ItemId";
|
||||
|
||||
private static readonly string[] _mediaAttachmentSaveColumns =
|
||||
{
|
||||
"ItemId",
|
||||
@@ -450,102 +459,15 @@ namespace Emby.Server.Implementations.Data
|
||||
"MIMEType"
|
||||
};
|
||||
|
||||
private static readonly string _mediaAttachmentSaveColumnsSelectQuery =
|
||||
$"select {string.Join(',', _mediaAttachmentSaveColumns)} from mediaattachments where ItemId=@ItemId";
|
||||
|
||||
private static readonly string _mediaAttachmentInsertPrefix;
|
||||
|
||||
private static string GetSaveItemCommandText()
|
||||
{
|
||||
var saveColumns = new[]
|
||||
{
|
||||
"guid",
|
||||
"type",
|
||||
"data",
|
||||
"Path",
|
||||
"StartDate",
|
||||
"EndDate",
|
||||
"ChannelId",
|
||||
"IsMovie",
|
||||
"IsSeries",
|
||||
"EpisodeTitle",
|
||||
"IsRepeat",
|
||||
"CommunityRating",
|
||||
"CustomRating",
|
||||
"IndexNumber",
|
||||
"IsLocked",
|
||||
"Name",
|
||||
"OfficialRating",
|
||||
"MediaType",
|
||||
"Overview",
|
||||
"ParentIndexNumber",
|
||||
"PremiereDate",
|
||||
"ProductionYear",
|
||||
"ParentId",
|
||||
"Genres",
|
||||
"InheritedParentalRatingValue",
|
||||
"SortName",
|
||||
"ForcedSortName",
|
||||
"RunTimeTicks",
|
||||
"Size",
|
||||
"DateCreated",
|
||||
"DateModified",
|
||||
"PreferredMetadataLanguage",
|
||||
"PreferredMetadataCountryCode",
|
||||
"Width",
|
||||
"Height",
|
||||
"DateLastRefreshed",
|
||||
"DateLastSaved",
|
||||
"IsInMixedFolder",
|
||||
"LockedFields",
|
||||
"Studios",
|
||||
"Audio",
|
||||
"ExternalServiceId",
|
||||
"Tags",
|
||||
"IsFolder",
|
||||
"UnratedType",
|
||||
"TopParentId",
|
||||
"TrailerTypes",
|
||||
"CriticRating",
|
||||
"CleanName",
|
||||
"PresentationUniqueKey",
|
||||
"OriginalTitle",
|
||||
"PrimaryVersionId",
|
||||
"DateLastMediaAdded",
|
||||
"Album",
|
||||
"IsVirtualItem",
|
||||
"SeriesName",
|
||||
"UserDataKey",
|
||||
"SeasonName",
|
||||
"SeasonId",
|
||||
"SeriesId",
|
||||
"ExternalSeriesId",
|
||||
"Tagline",
|
||||
"ProviderIds",
|
||||
"Images",
|
||||
"ProductionLocations",
|
||||
"ExtraIds",
|
||||
"TotalBitrate",
|
||||
"ExtraType",
|
||||
"Artists",
|
||||
"AlbumArtists",
|
||||
"ExternalId",
|
||||
"SeriesPresentationUniqueKey",
|
||||
"ShowId",
|
||||
"OwnerId"
|
||||
};
|
||||
|
||||
var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns) + ") values (";
|
||||
|
||||
for (var i = 0; i < saveColumns.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
saveItemCommandCommandText += ",";
|
||||
}
|
||||
|
||||
saveItemCommandCommandText += "@" + saveColumns[i];
|
||||
}
|
||||
|
||||
return saveItemCommandCommandText + ")";
|
||||
}
|
||||
private const string SaveItemCommandText =
|
||||
@"replace into TypedBaseItems
|
||||
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
|
||||
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
|
||||
|
||||
/// <summary>
|
||||
/// Save a standard item in the repo.
|
||||
@@ -636,7 +558,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
var statements = PrepareAll(db, new string[]
|
||||
{
|
||||
GetSaveItemCommandText(),
|
||||
SaveItemCommandText,
|
||||
"delete from AncestorIds where ItemId=@ItemId"
|
||||
}).ToList();
|
||||
|
||||
@@ -1110,7 +1032,8 @@ namespace Emby.Server.Implementations.Data
|
||||
continue;
|
||||
}
|
||||
|
||||
str.Append(ToValueString(i) + "|");
|
||||
str.Append(ToValueString(i))
|
||||
.Append('|');
|
||||
}
|
||||
|
||||
str.Length -= 1; // Remove last |
|
||||
@@ -1225,7 +1148,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = PrepareStatement(connection, "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems where guid = @guid"))
|
||||
using (var statement = PrepareStatement(connection, _retriveItemColumnsSelectQuery))
|
||||
{
|
||||
statement.TryBind("@guid", id);
|
||||
|
||||
@@ -2471,7 +2394,7 @@ namespace Emby.Server.Implementations.Data
|
||||
var item = query.SimilarTo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("(");
|
||||
builder.Append('(');
|
||||
|
||||
if (string.IsNullOrEmpty(item.OfficialRating))
|
||||
{
|
||||
@@ -2509,7 +2432,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!string.IsNullOrEmpty(query.SearchTerm))
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("(");
|
||||
builder.Append('(');
|
||||
|
||||
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
|
||||
|
||||
@@ -2775,82 +2698,82 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
private string FixUnicodeChars(string buffer)
|
||||
{
|
||||
if (buffer.IndexOf('\u2013') > -1)
|
||||
if (buffer.IndexOf('\u2013', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2013', '-'); // en dash
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2014') > -1)
|
||||
if (buffer.IndexOf('\u2014', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2014', '-'); // em dash
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2015') > -1)
|
||||
if (buffer.IndexOf('\u2015', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2015', '-'); // horizontal bar
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2017') > -1)
|
||||
if (buffer.IndexOf('\u2017', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2017', '_'); // double low line
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2018') > -1)
|
||||
if (buffer.IndexOf('\u2018', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2018', '\''); // left single quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2019') > -1)
|
||||
if (buffer.IndexOf('\u2019', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2019', '\''); // right single quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u201a') > -1)
|
||||
if (buffer.IndexOf('\u201a', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u201a', ','); // single low-9 quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u201b') > -1)
|
||||
if (buffer.IndexOf('\u201b', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u201b', '\''); // single high-reversed-9 quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u201c') > -1)
|
||||
if (buffer.IndexOf('\u201c', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u201c', '\"'); // left double quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u201d') > -1)
|
||||
if (buffer.IndexOf('\u201d', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u201d', '\"'); // right double quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u201e') > -1)
|
||||
if (buffer.IndexOf('\u201e', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u201e', '\"'); // double low-9 quotation mark
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2026') > -1)
|
||||
if (buffer.IndexOf('\u2026', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace("\u2026", "..."); // horizontal ellipsis
|
||||
buffer = buffer.Replace("\u2026", "...", StringComparison.Ordinal); // horizontal ellipsis
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2032') > -1)
|
||||
if (buffer.IndexOf('\u2032', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2032', '\''); // prime
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u2033') > -1)
|
||||
if (buffer.IndexOf('\u2033', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u2033', '\"'); // double prime
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u0060') > -1)
|
||||
if (buffer.IndexOf('\u0060', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u0060', '\''); // grave accent
|
||||
}
|
||||
|
||||
if (buffer.IndexOf('\u00B4') > -1)
|
||||
if (buffer.IndexOf('\u00B4', StringComparison.Ordinal) > -1)
|
||||
{
|
||||
buffer = buffer.Replace('\u00B4', '\''); // acute accent
|
||||
}
|
||||
@@ -2999,7 +2922,6 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
|
||||
var statements = PrepareAll(db, statementTexts).ToList();
|
||||
|
||||
if (!isReturningZeroItems)
|
||||
@@ -4669,8 +4591,12 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (query.BlockUnratedItems.Length > 1)
|
||||
{
|
||||
var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
|
||||
whereClauses.Add(string.Format("(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", inClause));
|
||||
var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
|
||||
whereClauses.Add(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))",
|
||||
inClause));
|
||||
}
|
||||
|
||||
if (query.ExcludeInheritedTags.Length > 0)
|
||||
@@ -4679,7 +4605,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (statement == null)
|
||||
{
|
||||
int index = 0;
|
||||
string excludedTags = string.Join(",", query.ExcludeInheritedTags.Select(t => paramName + index++));
|
||||
string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(t => paramName + index++));
|
||||
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
|
||||
}
|
||||
else
|
||||
@@ -5238,7 +5164,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
insertText.Append(",");
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||
@@ -5890,10 +5816,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
var cmdText = "select "
|
||||
+ string.Join(",", _mediaStreamSaveColumns)
|
||||
+ " from mediastreams where"
|
||||
+ " ItemId=@ItemId";
|
||||
var cmdText = _mediaStreamSaveColumnsSelectQuery;
|
||||
|
||||
if (query.Type.HasValue)
|
||||
{
|
||||
@@ -5972,15 +5895,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
while (startIndex < streams.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into mediastreams (");
|
||||
foreach (var column in _mediaStreamSaveColumns)
|
||||
{
|
||||
insertText.Append(column).Append(',');
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
insertText.Append(") values ");
|
||||
var insertText = new StringBuilder(_mediaStreamSaveColumnsInsertQuery);
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||
|
||||
@@ -6247,10 +6162,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
throw new ArgumentNullException(nameof(query));
|
||||
}
|
||||
|
||||
var cmdText = "select "
|
||||
+ string.Join(",", _mediaAttachmentSaveColumns)
|
||||
+ " from mediaattachments where"
|
||||
+ " ItemId=@ItemId";
|
||||
var cmdText = _mediaAttachmentSaveColumnsSelectQuery;
|
||||
|
||||
if (query.Index.HasValue)
|
||||
{
|
||||
@@ -6331,7 +6243,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
foreach (var column in _mediaAttachmentSaveColumns.Skip(1))
|
||||
{
|
||||
insertText.Append("@" + column + index + ",");
|
||||
insertText.Append('@')
|
||||
.Append(column)
|
||||
.Append(index)
|
||||
.Append(',');
|
||||
}
|
||||
|
||||
insertText.Length -= 1;
|
||||
|
||||
@@ -5,8 +5,8 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
@@ -17,16 +17,17 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Emby.Server.Implementations.Devices
|
||||
{
|
||||
public class DeviceManager : IDeviceManager
|
||||
{
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IAuthenticationRepository _authRepo;
|
||||
private readonly Dictionary<string, ClientCapabilities> _capabilitiesCache;
|
||||
private readonly object _capabilitiesSyncLock = new object();
|
||||
|
||||
public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
|
||||
@@ -35,13 +36,14 @@ namespace Emby.Server.Implementations.Devices
|
||||
IAuthenticationRepository authRepo,
|
||||
IJsonSerializer json,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager config)
|
||||
IServerConfigurationManager config,
|
||||
IMemoryCache memoryCache)
|
||||
{
|
||||
_json = json;
|
||||
_userManager = userManager;
|
||||
_config = config;
|
||||
_memoryCache = memoryCache;
|
||||
_authRepo = authRepo;
|
||||
_capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
|
||||
@@ -51,8 +53,7 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
lock (_capabilitiesSyncLock)
|
||||
{
|
||||
_capabilitiesCache[deviceId] = capabilities;
|
||||
|
||||
_memoryCache.CreateEntry(deviceId).SetValue(capabilities);
|
||||
_json.SerializeToFile(capabilities, path);
|
||||
}
|
||||
}
|
||||
@@ -71,13 +72,13 @@ namespace Emby.Server.Implementations.Devices
|
||||
|
||||
public ClientCapabilities GetCapabilities(string id)
|
||||
{
|
||||
if (_memoryCache.TryGetValue(id, out ClientCapabilities result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
lock (_capabilitiesSyncLock)
|
||||
{
|
||||
if (_capabilitiesCache.TryGetValue(id, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var path = Path.Combine(GetDevicePath(id), "capabilities.json");
|
||||
try
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IPNetwork2" Version="2.5.211" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
@@ -37,10 +37,10 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.2" />
|
||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.1" />
|
||||
<PackageReference Include="sharpcompress" Version="0.26.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="DotNet.Glob" Version="3.0.9" />
|
||||
</ItemGroup>
|
||||
@@ -53,7 +53,7 @@
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The _options.
|
||||
@@ -49,7 +48,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
|
||||
_streamHelper = streamHelper;
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
Path = path;
|
||||
_logger = logger;
|
||||
|
||||
@@ -449,7 +449,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpRes.StatusCode = 200;
|
||||
foreach(var (key, value) in GetDefaultCorsHeaders(httpReq))
|
||||
foreach (var (key, value) in GetDefaultCorsHeaders(httpReq))
|
||||
{
|
||||
httpRes.Headers.Add(key, value);
|
||||
}
|
||||
@@ -486,7 +486,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var handler = GetServiceHandler(httpReq);
|
||||
if (handler != null)
|
||||
{
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, _logger, cancellationToken).ConfigureAwait(false);
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -13,26 +13,22 @@ using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer.Security
|
||||
{
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ILogger<AuthService> _logger;
|
||||
private readonly IAuthorizationContext _authorizationContext;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public AuthService(
|
||||
ILogger<AuthService> logger,
|
||||
IAuthorizationContext authorizationContext,
|
||||
IServerConfigurationManager config,
|
||||
ISessionManager sessionManager,
|
||||
INetworkManager networkManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_authorizationContext = authorizationContext;
|
||||
_config = config;
|
||||
_sessionManager = sessionManager;
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.IO
|
||||
private readonly List<string> _affectedPaths = new List<string>();
|
||||
private readonly object _timerLock = new object();
|
||||
private Timer _timer;
|
||||
private bool _disposed;
|
||||
|
||||
public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
@@ -213,11 +214,11 @@ namespace Emby.Server.Implementations.IO
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
public void Dispose()
|
||||
{
|
||||
_disposed = true;
|
||||
DisposeTimer();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -25,14 +24,9 @@ namespace Emby.Server.Implementations.Images
|
||||
/// </summary>
|
||||
public class ArtistImageProvider : BaseDynamicImageProvider<MusicArtist>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
public ArtistImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor)
|
||||
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
|
||||
if (parent != null)
|
||||
{
|
||||
// Don't resolve these into audio files
|
||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename)
|
||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal)
|
||||
&& _libraryManager.IsAudioFile(filename))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -11,6 +11,17 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class ExclusiveLiveStream : ILiveStream
|
||||
{
|
||||
private readonly Func<Task> _closeFn;
|
||||
|
||||
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
|
||||
{
|
||||
MediaSource = mediaSource;
|
||||
EnableStreamSharing = false;
|
||||
_closeFn = closeFn;
|
||||
ConsumerCount = 1;
|
||||
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public int ConsumerCount { get; set; }
|
||||
|
||||
public string OriginalStreamId { get; set; }
|
||||
@@ -21,18 +32,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public MediaSourceInfo MediaSource { get; set; }
|
||||
|
||||
public string UniqueId { get; private set; }
|
||||
|
||||
private Func<Task> _closeFn;
|
||||
|
||||
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
|
||||
{
|
||||
MediaSource = mediaSource;
|
||||
EnableStreamSharing = false;
|
||||
_closeFn = closeFn;
|
||||
ConsumerCount = 1;
|
||||
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
public string UniqueId { get; }
|
||||
|
||||
public Task Close()
|
||||
{
|
||||
|
||||
@@ -18,7 +18,21 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
"**/small.jpg",
|
||||
"**/albumart.jpg",
|
||||
"**/*sample*",
|
||||
|
||||
// We have neither non-greedy matching or character group repetitions, working around that here.
|
||||
// https://github.com/dazinator/DotNet.Glob#patterns
|
||||
// .*/sample\..{1,5}
|
||||
"**/sample.?",
|
||||
"**/sample.??",
|
||||
"**/sample.???", // Matches sample.mkv
|
||||
"**/sample.????", // Matches sample.webm
|
||||
"**/sample.?????",
|
||||
"**/*.sample.?",
|
||||
"**/*.sample.??",
|
||||
"**/*.sample.???",
|
||||
"**/*.sample.????",
|
||||
"**/*.sample.?????",
|
||||
"**/sample/*",
|
||||
|
||||
// Directories
|
||||
"**/metadata/**",
|
||||
@@ -64,10 +78,13 @@ namespace Emby.Server.Implementations.Library
|
||||
"**/.grab/**",
|
||||
"**/.grab",
|
||||
|
||||
// Unix hidden files and directories
|
||||
"**/.*/**",
|
||||
// Unix hidden files
|
||||
"**/.*",
|
||||
|
||||
// Mac - if you ever remove the above.
|
||||
// "**/._*",
|
||||
// "**/.DS_Store",
|
||||
|
||||
// thumbs.db
|
||||
"**/thumbs.db",
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -46,11 +45,11 @@ using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using MediaBrowser.Providers.MediaInfo;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
using Genre = MediaBrowser.Controller.Entities.Genre;
|
||||
using Person = MediaBrowser.Controller.Entities.Person;
|
||||
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
||||
using VideoResolver = Emby.Naming.Video.VideoResolver;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@@ -60,7 +59,10 @@ namespace Emby.Server.Implementations.Library
|
||||
/// </summary>
|
||||
public class LibraryManager : ILibraryManager
|
||||
{
|
||||
private const string ShortcutFileExtension = ".mblink";
|
||||
|
||||
private readonly ILogger<LibraryManager> _logger;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataManager _userDataRepository;
|
||||
@@ -72,12 +74,118 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IItemRepository _itemRepository;
|
||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder sync lock.
|
||||
/// </summary>
|
||||
private readonly object _rootFolderSyncLock = new object();
|
||||
private readonly object _userRootFolderSyncLock = new object();
|
||||
|
||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||
|
||||
private NamingOptions _namingOptions;
|
||||
private string[] _videoFileExtensions;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder.
|
||||
/// </summary>
|
||||
private volatile AggregateFolder _rootFolder;
|
||||
private volatile UserRootFolder _userRootFolder;
|
||||
|
||||
private bool _wizardCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="taskManager">The task manager.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="userDataRepository">The user data repository.</param>
|
||||
/// <param name="libraryMonitorFactory">The library monitor.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="providerManagerFactory">The provider manager.</param>
|
||||
/// <param name="userviewManagerFactory">The userview manager.</param>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
/// <param name="itemRepository">The item repository.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
/// <param name="memoryCache">The memory cache.</param>
|
||||
public LibraryManager(
|
||||
IServerApplicationHost appHost,
|
||||
ILogger<LibraryManager> logger,
|
||||
ITaskManager taskManager,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IUserDataManager userDataRepository,
|
||||
Lazy<ILibraryMonitor> libraryMonitorFactory,
|
||||
IFileSystem fileSystem,
|
||||
Lazy<IProviderManager> providerManagerFactory,
|
||||
Lazy<IUserViewManager> userviewManagerFactory,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepository,
|
||||
IImageProcessor imageProcessor,
|
||||
IMemoryCache memoryCache)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_taskManager = taskManager;
|
||||
_userManager = userManager;
|
||||
_configurationManager = configurationManager;
|
||||
_userDataRepository = userDataRepository;
|
||||
_libraryMonitorFactory = libraryMonitorFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_providerManagerFactory = providerManagerFactory;
|
||||
_userviewManagerFactory = userviewManagerFactory;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepository = itemRepository;
|
||||
_imageProcessor = imageProcessor;
|
||||
_memoryCache = memoryCache;
|
||||
|
||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
RecordConfigurationValues(configurationManager.Configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item added].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item updated].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item removed].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root folder.
|
||||
/// </summary>
|
||||
/// <value>The root folder.</value>
|
||||
public AggregateFolder RootFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rootFolder == null)
|
||||
{
|
||||
lock (_rootFolderSyncLock)
|
||||
{
|
||||
if (_rootFolder == null)
|
||||
{
|
||||
_rootFolder = CreateRootFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _rootFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
|
||||
|
||||
private IProviderManager ProviderManager => _providerManagerFactory.Value;
|
||||
@@ -116,75 +224,8 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <value>The comparers.</value>
|
||||
private IBaseItemComparer[] Comparers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item added].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item updated].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when [item removed].
|
||||
/// </summary>
|
||||
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
||||
|
||||
public bool IsScanRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="taskManager">The task manager.</param>
|
||||
/// <param name="userManager">The user manager.</param>
|
||||
/// <param name="configurationManager">The configuration manager.</param>
|
||||
/// <param name="userDataRepository">The user data repository.</param>
|
||||
/// <param name="libraryMonitorFactory">The library monitor.</param>
|
||||
/// <param name="fileSystem">The file system.</param>
|
||||
/// <param name="providerManagerFactory">The provider manager.</param>
|
||||
/// <param name="userviewManagerFactory">The userview manager.</param>
|
||||
/// <param name="mediaEncoder">The media encoder.</param>
|
||||
/// <param name="itemRepository">The item repository.</param>
|
||||
/// <param name="imageProcessor">The image processor.</param>
|
||||
public LibraryManager(
|
||||
IServerApplicationHost appHost,
|
||||
ILogger<LibraryManager> logger,
|
||||
ITaskManager taskManager,
|
||||
IUserManager userManager,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IUserDataManager userDataRepository,
|
||||
Lazy<ILibraryMonitor> libraryMonitorFactory,
|
||||
IFileSystem fileSystem,
|
||||
Lazy<IProviderManager> providerManagerFactory,
|
||||
Lazy<IUserViewManager> userviewManagerFactory,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepository,
|
||||
IImageProcessor imageProcessor)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_taskManager = taskManager;
|
||||
_userManager = userManager;
|
||||
_configurationManager = configurationManager;
|
||||
_userDataRepository = userDataRepository;
|
||||
_libraryMonitorFactory = libraryMonitorFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_providerManagerFactory = providerManagerFactory;
|
||||
_userviewManagerFactory = userviewManagerFactory;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepository = itemRepository;
|
||||
_imageProcessor = imageProcessor;
|
||||
|
||||
_libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
|
||||
|
||||
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
|
||||
|
||||
RecordConfigurationValues(configurationManager.Configuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
@@ -208,41 +249,6 @@ namespace Emby.Server.Implementations.Library
|
||||
PostscanTasks = postscanTasks.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder.
|
||||
/// </summary>
|
||||
private volatile AggregateFolder _rootFolder;
|
||||
|
||||
/// <summary>
|
||||
/// The _root folder sync lock.
|
||||
/// </summary>
|
||||
private readonly object _rootFolderSyncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root folder.
|
||||
/// </summary>
|
||||
/// <value>The root folder.</value>
|
||||
public AggregateFolder RootFolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rootFolder == null)
|
||||
{
|
||||
lock (_rootFolderSyncLock)
|
||||
{
|
||||
if (_rootFolder == null)
|
||||
{
|
||||
_rootFolder = CreateRootFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _rootFolder;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _wizardCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Records the configuration values.
|
||||
/// </summary>
|
||||
@@ -293,7 +299,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
_libraryItemsCache.AddOrUpdate(item.Id, item, delegate { return item; });
|
||||
_memoryCache.CreateEntry(item.Id).SetValue(item);
|
||||
}
|
||||
|
||||
public void DeleteItem(BaseItem item, DeleteOptions options)
|
||||
@@ -441,7 +447,7 @@ namespace Emby.Server.Implementations.Library
|
||||
_itemRepository.DeleteItem(child.Id);
|
||||
}
|
||||
|
||||
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
||||
_memoryCache.Remove(item.Id);
|
||||
|
||||
ReportItemRemoved(item, parent);
|
||||
}
|
||||
@@ -511,8 +517,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
// Try to normalize paths located underneath program-data in an attempt to make them more portable
|
||||
key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
|
||||
.TrimStart(new[] { '/', '\\' })
|
||||
.Replace("/", "\\");
|
||||
.TrimStart('/', '\\')
|
||||
.Replace('/', '\\');
|
||||
}
|
||||
|
||||
if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
|
||||
@@ -775,14 +781,11 @@ namespace Emby.Server.Implementations.Library
|
||||
return rootFolder;
|
||||
}
|
||||
|
||||
private volatile UserRootFolder _userRootFolder;
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
public Folder GetUserRootFolder()
|
||||
{
|
||||
if (_userRootFolder == null)
|
||||
{
|
||||
lock (_syncLock)
|
||||
lock (_userRootFolderSyncLock)
|
||||
{
|
||||
if (_userRootFolder == null)
|
||||
{
|
||||
@@ -1245,7 +1248,7 @@ namespace Emby.Server.Implementations.Library
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
||||
if (_libraryItemsCache.TryGetValue(id, out BaseItem item))
|
||||
if (_memoryCache.TryGetValue(id, out BaseItem item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
@@ -1332,7 +1335,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = _itemRepository.GetItemList(query).ToArray()
|
||||
Items = _itemRepository.GetItemList(query)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1463,11 +1466,9 @@ namespace Emby.Server.Implementations.Library
|
||||
return _itemRepository.GetItems(query);
|
||||
}
|
||||
|
||||
var list = _itemRepository.GetItemList(query);
|
||||
|
||||
return new QueryResult<BaseItem>
|
||||
{
|
||||
Items = list
|
||||
Items = _itemRepository.GetItemList(query)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1590,7 +1591,6 @@ namespace Emby.Server.Implementations.Library
|
||||
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
var tasks = IntroProviders
|
||||
.OrderBy(i => i.GetType().Name.Contains("Default", StringComparison.OrdinalIgnoreCase) ? 1 : 0)
|
||||
.Take(1)
|
||||
.Select(i => GetIntros(i, item, user));
|
||||
|
||||
@@ -1876,7 +1876,8 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||
if (outdated.Length == 0)
|
||||
// Skip image processing if current or live tv source
|
||||
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
|
||||
{
|
||||
RegisterItem(item);
|
||||
return;
|
||||
@@ -1945,12 +1946,9 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <summary>
|
||||
/// Updates the item.
|
||||
/// </summary>
|
||||
public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||
{
|
||||
// Don't iterate multiple times
|
||||
var itemsList = items.ToList();
|
||||
|
||||
foreach (var item in itemsList)
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.IsFileProtocol)
|
||||
{
|
||||
@@ -1962,11 +1960,11 @@ namespace Emby.Server.Implementations.Library
|
||||
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
if (ItemUpdated != null)
|
||||
{
|
||||
foreach (var item in itemsList)
|
||||
foreach (var item in items)
|
||||
{
|
||||
// With the live tv guide this just creates too much noise
|
||||
if (item.SourceType != SourceType.Library)
|
||||
@@ -2189,8 +2187,6 @@ namespace Emby.Server.Implementations.Library
|
||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||
|
||||
public UserView GetNamedView(
|
||||
User user,
|
||||
string name,
|
||||
@@ -2488,14 +2484,9 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
||||
|
||||
var episodeInfo = episode.IsFileProtocol ?
|
||||
resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) :
|
||||
new Naming.TV.EpisodeInfo();
|
||||
|
||||
if (episodeInfo == null)
|
||||
{
|
||||
episodeInfo = new Naming.TV.EpisodeInfo();
|
||||
}
|
||||
var episodeInfo = episode.IsFileProtocol
|
||||
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo()
|
||||
: new Naming.TV.EpisodeInfo();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -2503,11 +2494,13 @@ namespace Emby.Server.Implementations.Library
|
||||
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Read from metadata
|
||||
var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
{
|
||||
MediaSource = episode.GetMediaSources(false)[0],
|
||||
MediaType = DlnaProfileType.Video
|
||||
}, CancellationToken.None).GetAwaiter().GetResult();
|
||||
var mediaInfo = _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
MediaSource = episode.GetMediaSources(false)[0],
|
||||
MediaType = DlnaProfileType.Video
|
||||
},
|
||||
CancellationToken.None).GetAwaiter().GetResult();
|
||||
if (mediaInfo.ParentIndexNumber > 0)
|
||||
{
|
||||
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
|
||||
@@ -2665,7 +2658,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase));
|
||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (currentVideo != null)
|
||||
{
|
||||
@@ -2682,9 +2675,7 @@ namespace Emby.Server.Implementations.Library
|
||||
.Select(video =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = GetItemById(video.Id) as Trailer;
|
||||
|
||||
if (dbItem != null)
|
||||
if (GetItemById(video.Id) is Trailer dbItem)
|
||||
{
|
||||
video = dbItem;
|
||||
}
|
||||
@@ -3011,8 +3002,6 @@ namespace Emby.Server.Implementations.Library
|
||||
});
|
||||
}
|
||||
|
||||
private const string ShortcutFileExtension = ".mblink";
|
||||
|
||||
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
|
||||
{
|
||||
AddMediaPathInternal(virtualFolderName, pathInfo, true);
|
||||
@@ -3206,7 +3195,8 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!Directory.Exists(virtualFolderPath))
|
||||
{
|
||||
throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
|
||||
throw new FileNotFoundException(
|
||||
string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName));
|
||||
}
|
||||
|
||||
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
|
||||
|
||||
@@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private IJsonSerializer _json;
|
||||
private IApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
|
||||
{
|
||||
@@ -72,13 +71,14 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
mediaSource.AnalyzeDurationMs = 3000;
|
||||
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||
ExtractChapters = false
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
mediaInfo = await _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||
ExtractChapters = false
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (cacheFilePath != null)
|
||||
{
|
||||
@@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.RunTimeTicks = null;
|
||||
}
|
||||
|
||||
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
|
||||
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
if (audioStream == null || audioStream.Index == -1)
|
||||
{
|
||||
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library
|
||||
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
||||
}
|
||||
|
||||
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
|
||||
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||
if (videoStream != null)
|
||||
{
|
||||
if (!videoStream.BitRate.HasValue)
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class MediaSourceManager : IMediaSourceManager, IDisposable
|
||||
{
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char LiveStreamIdDelimeter = '_';
|
||||
|
||||
private readonly IItemRepository _itemRepo;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@@ -40,6 +43,9 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly ILocalizationManager _localizationManager;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
private IMediaSourceProvider[] _providers;
|
||||
|
||||
public MediaSourceManager(
|
||||
@@ -368,7 +374,6 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
||||
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
||||
|
||||
@@ -451,9 +456,6 @@ namespace Emby.Server.Implementations.Library
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||
|
||||
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
@@ -619,12 +621,14 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (liveStreamInfo is IDirectStreamProvider)
|
||||
{
|
||||
var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
ExtractChapters = false,
|
||||
MediaType = DlnaProfileType.Video
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
var info = await _mediaEncoder.GetMediaInfo(
|
||||
new MediaInfoRequest
|
||||
{
|
||||
MediaSource = mediaSource,
|
||||
ExtractChapters = false,
|
||||
MediaType = DlnaProfileType.Video
|
||||
},
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
mediaSource.MediaStreams = info.MediaStreams;
|
||||
mediaSource.Container = info.Container;
|
||||
@@ -855,24 +859,21 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const char LiveStreamIdDelimeter = '_';
|
||||
|
||||
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
|
||||
private (IMediaSourceProvider, string) GetProvider(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
{
|
||||
throw new ArgumentException("key");
|
||||
throw new ArgumentException("Key can't be empty.", nameof(key));
|
||||
}
|
||||
|
||||
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
|
||||
|
||||
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture), keys[0], StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var splitIndex = key.IndexOf(LiveStreamIdDelimeter);
|
||||
var splitIndex = key.IndexOf(LiveStreamIdDelimeter, StringComparison.Ordinal);
|
||||
var keyId = key.Substring(splitIndex + 1);
|
||||
|
||||
return new Tuple<IMediaSourceProvider, string>(provider, keyId);
|
||||
return (provider, keyId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -881,9 +882,9 @@ namespace Emby.Server.Implementations.Library
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private readonly object _disposeLock = new object();
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
@@ -892,15 +893,12 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
lock (_disposeLock)
|
||||
foreach (var key in _openStreams.Keys.ToList())
|
||||
{
|
||||
foreach (var key in _openStreams.Keys.ToList())
|
||||
{
|
||||
var task = CloseLiveStream(key);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
CloseLiveStream(key).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
_liveStreamSemaphore.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
// load forced subs if we have found no suitable full subtitles
|
||||
stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
|
||||
stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
|
||||
@@ -4,12 +4,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Globalization;
|
||||
using Emby.Naming.TV;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
@@ -13,7 +12,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// </summary>
|
||||
public class SeasonResolver : FolderResolver<Season>
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly ILogger<SeasonResolver> _logger;
|
||||
@@ -21,17 +19,14 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
|
||||
/// </summary>
|
||||
/// <param name="config">The config.</param>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
/// <param name="localization">The localization.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public SeasonResolver(
|
||||
IServerConfigurationManager config,
|
||||
ILibraryManager libraryManager,
|
||||
ILocalizationManager localization,
|
||||
ILogger<SeasonResolver> logger)
|
||||
{
|
||||
_config = config;
|
||||
_libraryManager = libraryManager;
|
||||
_localization = localization;
|
||||
_logger = logger;
|
||||
|
||||
@@ -4,12 +4,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Search;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -20,13 +20,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
public class SearchEngine : ISearchEngine
|
||||
{
|
||||
private readonly ILogger<SearchEngine> _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IUserManager _userManager;
|
||||
|
||||
public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
|
||||
public SearchEngine(ILibraryManager libraryManager, IUserManager userManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
@@ -34,11 +32,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||
{
|
||||
User user = null;
|
||||
|
||||
if (query.UserId.Equals(Guid.Empty))
|
||||
{
|
||||
}
|
||||
else
|
||||
if (query.UserId != Guid.Empty)
|
||||
{
|
||||
user = _userManager.GetUserById(query.UserId);
|
||||
}
|
||||
@@ -48,19 +42,19 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (query.StartIndex.HasValue)
|
||||
{
|
||||
results = results.Skip(query.StartIndex.Value).ToList();
|
||||
results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value);
|
||||
}
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
results = results.Take(query.Limit.Value).ToList();
|
||||
results = results.GetRange(0, query.Limit.Value);
|
||||
}
|
||||
|
||||
return new QueryResult<SearchHintInfo>
|
||||
{
|
||||
TotalRecordCount = totalRecordCount,
|
||||
|
||||
Items = results.ToArray()
|
||||
Items = results
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
|
||||
throw new ArgumentException("SearchTerm can't be empty.", nameof(query));
|
||||
}
|
||||
|
||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||
|
||||
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Book = MediaBrowser.Controller.Entities.Book;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@@ -28,18 +27,15 @@ namespace Emby.Server.Implementations.Library
|
||||
private readonly ConcurrentDictionary<string, UserItemData> _userData =
|
||||
new ConcurrentDictionary<string, UserItemData>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private readonly ILogger<UserDataManager> _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IUserDataRepository _repository;
|
||||
|
||||
public UserDataManager(
|
||||
ILogger<UserDataManager> logger,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
IUserDataRepository repository)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_userManager = userManager;
|
||||
_repository = repository;
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
||||
@@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
|
||||
ListingsProviderInfo info,
|
||||
List<string> programIds,
|
||||
CancellationToken cancellationToken)
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (programIds.Count == 0)
|
||||
{
|
||||
@@ -474,7 +474,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
var imageId = i.Substring(0, 10);
|
||||
|
||||
if (!imageIdString.Contains(imageId))
|
||||
if (!imageIdString.Contains(imageId, StringComparison.Ordinal))
|
||||
{
|
||||
imageIdString += "\"" + imageId + "\",";
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Library;
|
||||
@@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
@@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly LiveTvDtoService _tvDtoService;
|
||||
@@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
ILibraryManager libraryManager,
|
||||
ITaskManager taskManager,
|
||||
ILocalizationManager localization,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IChannelManager channelManager,
|
||||
LiveTvDtoService liveTvDtoService)
|
||||
@@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
_libraryManager = libraryManager;
|
||||
_taskManager = taskManager;
|
||||
_localization = localization;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_dtoService = dtoService;
|
||||
_userDataManager = userDataManager;
|
||||
@@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||
{
|
||||
info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info));
|
||||
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info));
|
||||
|
||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
@@ -2278,7 +2275,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
|
||||
info = _jsonSerializer.DeserializeFromString<ListingsProviderInfo>(_jsonSerializer.SerializeToString(info));
|
||||
info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info));
|
||||
|
||||
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
while (!sr.EndOfStream)
|
||||
{
|
||||
string line = StripXML(sr.ReadLine());
|
||||
if (line.Contains("Channel"))
|
||||
if (line.Contains("Channel", StringComparison.Ordinal))
|
||||
{
|
||||
LiveTvTunerStatus status;
|
||||
var index = line.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
||||
@@ -226,6 +226,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
private static string StripXML(string source)
|
||||
{
|
||||
if (string.IsNullOrEmpty(source))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
char[] buffer = new char[source.Length];
|
||||
int bufferIndex = 0;
|
||||
bool inside = false;
|
||||
@@ -270,7 +275,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
for (int i = 0; i < model.TunerCount; ++i)
|
||||
{
|
||||
var name = string.Format("Tuner {0}", i + 1);
|
||||
var name = string.Format(CultureInfo.InvariantCulture, "Tuner {0}", i + 1);
|
||||
var currentChannel = "none"; // @todo Get current channel and map back to Station Id
|
||||
var isAvailable = await manager.CheckTunerAvailability(ipInfo, i, cancellationToken).ConfigureAwait(false);
|
||||
var status = isAvailable ? LiveTvTunerStatus.Available : LiveTvTunerStatus.LiveTv;
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
public class HdHomerunManager : IDisposable
|
||||
public sealed class HdHomerunManager : IDisposable
|
||||
{
|
||||
public const int HdHomeRunPort = 65001;
|
||||
|
||||
@@ -105,6 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
StopStreaming(socket).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<bool> CheckTunerAvailability(IPAddress remoteIp, int tuner, CancellationToken cancellationToken)
|
||||
@@ -162,7 +164,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
|
||||
_activeTuner = i;
|
||||
var lockKeyString = string.Format("{0:d}", lockKeyValue);
|
||||
var lockKeyString = string.Format(CultureInfo.InvariantCulture, "{0:d}", lockKeyValue);
|
||||
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
|
||||
await stream.WriteAsync(lockkeyMsg, 0, lockkeyMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||
int receivedBytes = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
@@ -173,8 +175,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
continue;
|
||||
}
|
||||
|
||||
var commandList = commands.GetCommands();
|
||||
foreach (var command in commandList)
|
||||
foreach (var command in commands.GetCommands())
|
||||
{
|
||||
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
|
||||
await stream.WriteAsync(channelMsg, 0, channelMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||
@@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
var targetValue = string.Format("rtp://{0}:{1}", localIp, localPort);
|
||||
var targetValue = string.Format(CultureInfo.InvariantCulture, "rtp://{0}:{1}", localIp, localPort);
|
||||
var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
|
||||
|
||||
await stream.WriteAsync(targetMsg, 0, targetMsg.Length, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -158,15 +158,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
private string GetChannelNumber(string extInf, Dictionary<string, string> attributes, string mediaUrl)
|
||||
{
|
||||
var nameParts = extInf.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts[nameParts.Length - 1].Trim() : null;
|
||||
var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].AsSpan().Trim() : ReadOnlySpan<char>.Empty;
|
||||
|
||||
string numberString = null;
|
||||
string attributeValue;
|
||||
double doubleValue;
|
||||
|
||||
if (attributes.TryGetValue("tvg-chno", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
numberString = attributeValue;
|
||||
}
|
||||
@@ -176,36 +175,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
if (attributes.TryGetValue("tvg-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
numberString = attributeValue;
|
||||
}
|
||||
else if (attributes.TryGetValue("channel-id", out attributeValue))
|
||||
{
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue))
|
||||
if (double.TryParse(attributeValue, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
numberString = attributeValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(numberString))
|
||||
if (string.IsNullOrWhiteSpace(numberString))
|
||||
{
|
||||
// Using this as a fallback now as this leads to Problems with channels like "5 USA"
|
||||
// where 5 isnt ment to be the channel number
|
||||
// Check for channel number with the format from SatIp
|
||||
// #EXTINF:0,84. VOX Schweiz
|
||||
// #EXTINF:0,84.0 - VOX Schweiz
|
||||
if (!string.IsNullOrWhiteSpace(nameInExtInf))
|
||||
if (!nameInExtInf.IsEmpty && !nameInExtInf.IsWhiteSpace())
|
||||
{
|
||||
var numberIndex = nameInExtInf.IndexOf(' ');
|
||||
if (numberIndex > 0)
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
var numberPart = nameInExtInf.Slice(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
numberString = numberPart;
|
||||
numberString = numberPart.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,7 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
try
|
||||
{
|
||||
numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last());
|
||||
numberString = Path.GetFileNameWithoutExtension(mediaUrl.Split('/')[^1]);
|
||||
|
||||
if (!IsValidChannelNumber(numberString))
|
||||
{
|
||||
@@ -258,7 +257,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
|
||||
if (!double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -281,7 +280,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
var numberPart = nameInExtInf.Substring(0, numberIndex).Trim(new[] { ' ', '.' });
|
||||
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out var number))
|
||||
if (double.TryParse(numberPart, NumberStyles.Any, CultureInfo.InvariantCulture, out _))
|
||||
{
|
||||
// channel.Number = number.ToString();
|
||||
nameInExtInf = nameInExtInf.Substring(numberIndex + 1).Trim(new[] { ' ', '-' });
|
||||
|
||||
@@ -101,8 +101,8 @@
|
||||
"TaskCleanTranscode": "Lösche Transkodier Pfad",
|
||||
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
|
||||
"TaskRefreshPeople": "Erneuere Schausteller",
|
||||
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
|
||||
"TaskRefreshPeople": "Erneuere Schauspieler",
|
||||
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
|
||||
"TaskCleanLogs": "Lösche Log Pfad",
|
||||
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"HeaderRecordingGroups": "錄製組",
|
||||
"Inherit": "繼承",
|
||||
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
|
||||
"TaskDownloadMissingSubtitlesDescription": "在網路上透過描述資料搜尋遺失的字幕。",
|
||||
"TaskDownloadMissingSubtitlesDescription": "在網路上透過中繼資料搜尋遺失的字幕。",
|
||||
"TaskDownloadMissingSubtitles": "下載遺失的字幕",
|
||||
"TaskRefreshChannels": "重新整理頻道",
|
||||
"TaskUpdatePlugins": "更新插件",
|
||||
|
||||
@@ -247,7 +247,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
}
|
||||
|
||||
// Try splitting by : to handle "Germany: FSK 18"
|
||||
var index = rating.IndexOf(':');
|
||||
var index = rating.IndexOf(':', StringComparison.Ordinal);
|
||||
if (index != -1)
|
||||
{
|
||||
rating = rating.Substring(index).TrimStart(':').Trim();
|
||||
@@ -312,12 +312,12 @@ namespace Emby.Server.Implementations.Localization
|
||||
throw new ArgumentNullException(nameof(culture));
|
||||
}
|
||||
|
||||
const string prefix = "Core";
|
||||
var key = prefix + culture;
|
||||
const string Prefix = "Core";
|
||||
var key = Prefix + culture;
|
||||
|
||||
return _dictionaries.GetOrAdd(
|
||||
key,
|
||||
f => GetDictionary(prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
|
||||
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> GetDictionary(string prefix, string culture, string baseFilename)
|
||||
|
||||
@@ -15,13 +15,11 @@ namespace Emby.Server.Implementations.Net
|
||||
public sealed class UdpSocket : ISocket, IDisposable
|
||||
{
|
||||
private Socket _socket;
|
||||
private int _localPort;
|
||||
private readonly int _localPort;
|
||||
private bool _disposed = false;
|
||||
|
||||
public Socket Socket => _socket;
|
||||
|
||||
public IPAddress LocalIPAddress { get; }
|
||||
|
||||
private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs()
|
||||
{
|
||||
SocketFlags = SocketFlags.None
|
||||
@@ -51,18 +49,33 @@ namespace Emby.Server.Implementations.Net
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
|
||||
public UdpSocket(Socket socket, IPEndPoint endPoint)
|
||||
{
|
||||
if (socket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(socket));
|
||||
}
|
||||
|
||||
_socket = socket;
|
||||
_socket.Connect(endPoint);
|
||||
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
|
||||
public IPAddress LocalIPAddress { get; }
|
||||
|
||||
private void InitReceiveSocketAsyncEventArgs()
|
||||
{
|
||||
var receiveBuffer = new byte[8192];
|
||||
_receiveSocketAsyncEventArgs.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
|
||||
_receiveSocketAsyncEventArgs.Completed += _receiveSocketAsyncEventArgs_Completed;
|
||||
_receiveSocketAsyncEventArgs.Completed += OnReceiveSocketAsyncEventArgsCompleted;
|
||||
|
||||
var sendBuffer = new byte[8192];
|
||||
_sendSocketAsyncEventArgs.SetBuffer(sendBuffer, 0, sendBuffer.Length);
|
||||
_sendSocketAsyncEventArgs.Completed += _sendSocketAsyncEventArgs_Completed;
|
||||
_sendSocketAsyncEventArgs.Completed += OnSendSocketAsyncEventArgsCompleted;
|
||||
}
|
||||
|
||||
private void _receiveSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
|
||||
private void OnReceiveSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
var tcs = _currentReceiveTaskCompletionSource;
|
||||
if (tcs != null)
|
||||
@@ -86,7 +99,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
}
|
||||
|
||||
private void _sendSocketAsyncEventArgs_Completed(object sender, SocketAsyncEventArgs e)
|
||||
private void OnSendSocketAsyncEventArgsCompleted(object sender, SocketAsyncEventArgs e)
|
||||
{
|
||||
var tcs = _currentSendTaskCompletionSource;
|
||||
if (tcs != null)
|
||||
@@ -104,19 +117,6 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
}
|
||||
|
||||
public UdpSocket(Socket socket, IPEndPoint endPoint)
|
||||
{
|
||||
if (socket == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(socket));
|
||||
}
|
||||
|
||||
_socket = socket;
|
||||
_socket.Connect(endPoint);
|
||||
|
||||
InitReceiveSocketAsyncEventArgs();
|
||||
}
|
||||
|
||||
public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
@@ -247,6 +247,7 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
@@ -255,6 +256,8 @@ namespace Emby.Server.Implementations.Net
|
||||
}
|
||||
|
||||
_socket?.Dispose();
|
||||
_receiveSocketAsyncEventArgs.Dispose();
|
||||
_sendSocketAsyncEventArgs.Dispose();
|
||||
_currentReceiveTaskCompletionSource?.TrySetCanceled();
|
||||
_currentSendTaskCompletionSource?.TrySetCanceled();
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ namespace Emby.Server.Implementations.Networking
|
||||
(octet[0] == 127) || // RFC1122
|
||||
(octet[0] == 169 && octet[1] == 254)) // RFC3927
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkSubnets && IsInPrivateAddressSpaceAndLocalSubnet(endpoint))
|
||||
@@ -390,7 +390,7 @@ namespace Emby.Server.Implementations.Networking
|
||||
var host = uri.DnsSafeHost;
|
||||
_logger.LogDebug("Resolving host {0}", host);
|
||||
|
||||
address = GetIpAddresses(host).Result.FirstOrDefault();
|
||||
address = GetIpAddresses(host).GetAwaiter().GetResult().FirstOrDefault();
|
||||
|
||||
if (address != null)
|
||||
{
|
||||
|
||||
@@ -349,16 +349,14 @@ namespace Emby.Server.Implementations.Playlists
|
||||
AlbumTitle = child.Album
|
||||
};
|
||||
|
||||
var hasAlbumArtist = child as IHasAlbumArtist;
|
||||
if (hasAlbumArtist != null)
|
||||
if (child is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
|
||||
}
|
||||
|
||||
var hasArtist = child as IHasArtist;
|
||||
if (hasArtist != null)
|
||||
if (child is IHasArtist hasArtist)
|
||||
{
|
||||
entry.TrackArtist = hasArtist.Artists.FirstOrDefault();
|
||||
entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
|
||||
}
|
||||
|
||||
if (child.RunTimeTicks.HasValue)
|
||||
@@ -385,16 +383,14 @@ namespace Emby.Server.Implementations.Playlists
|
||||
AlbumTitle = child.Album
|
||||
};
|
||||
|
||||
var hasAlbumArtist = child as IHasAlbumArtist;
|
||||
if (hasAlbumArtist != null)
|
||||
if (child is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
|
||||
}
|
||||
|
||||
var hasArtist = child as IHasArtist;
|
||||
if (hasArtist != null)
|
||||
if (child is IHasArtist hasArtist)
|
||||
{
|
||||
entry.TrackArtist = hasArtist.Artists.FirstOrDefault();
|
||||
entry.TrackArtist = hasArtist.Artists.Count > 0 ? hasArtist.Artists[0] : null;
|
||||
}
|
||||
|
||||
if (child.RunTimeTicks.HasValue)
|
||||
@@ -411,8 +407,10 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var playlist = new M3uPlaylist();
|
||||
playlist.IsExtended = true;
|
||||
var playlist = new M3uPlaylist
|
||||
{
|
||||
IsExtended = true
|
||||
};
|
||||
foreach (var child in item.GetLinkedChildren())
|
||||
{
|
||||
var entry = new M3uPlaylistEntry()
|
||||
@@ -422,10 +420,9 @@ namespace Emby.Server.Implementations.Playlists
|
||||
Album = child.Album
|
||||
};
|
||||
|
||||
var hasAlbumArtist = child as IHasAlbumArtist;
|
||||
if (hasAlbumArtist != null)
|
||||
if (child is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
|
||||
}
|
||||
|
||||
if (child.RunTimeTicks.HasValue)
|
||||
@@ -453,10 +450,9 @@ namespace Emby.Server.Implementations.Playlists
|
||||
Album = child.Album
|
||||
};
|
||||
|
||||
var hasAlbumArtist = child as IHasAlbumArtist;
|
||||
if (hasAlbumArtist != null)
|
||||
if (child is IHasAlbumArtist hasAlbumArtist)
|
||||
{
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
|
||||
entry.AlbumArtist = hasAlbumArtist.AlbumArtists.Count > 0 ? hasAlbumArtist.AlbumArtists[0] : null;
|
||||
}
|
||||
|
||||
if (child.RunTimeTicks.HasValue)
|
||||
@@ -514,7 +510,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
if (!folderPath.EndsWith(Path.DirectorySeparatorChar))
|
||||
{
|
||||
folderPath = folderPath + Path.DirectorySeparatorChar;
|
||||
folderPath += Path.DirectorySeparatorChar;
|
||||
}
|
||||
|
||||
var folderUri = new Uri(folderPath);
|
||||
@@ -537,32 +533,12 @@ namespace Emby.Server.Implementations.Playlists
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
private static string UnEscape(string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return content;
|
||||
}
|
||||
|
||||
return content.Replace("&", "&").Replace("'", "'").Replace(""", "\"").Replace(">", ">").Replace("<", "<");
|
||||
}
|
||||
|
||||
private static string Escape(string content)
|
||||
{
|
||||
if (content == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return content.Replace("&", "&").Replace("'", "'").Replace("\"", """).Replace(">", ">").Replace("<", "<");
|
||||
}
|
||||
|
||||
public Folder GetPlaylistsFolder(Guid userId)
|
||||
{
|
||||
var typeName = "PlaylistsFolder";
|
||||
const string TypeName = "PlaylistsFolder";
|
||||
|
||||
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ??
|
||||
_libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal));
|
||||
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ??
|
||||
_libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -37,7 +36,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IApplicationPaths _applicationPaths;
|
||||
private readonly ILogger<TaskManager> _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskManager" /> class.
|
||||
@@ -45,17 +43,14 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// <param name="applicationPaths">The application paths.</param>
|
||||
/// <param name="jsonSerializer">The json serializer.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="fileSystem">The filesystem manager.</param>
|
||||
public TaskManager(
|
||||
IApplicationPaths applicationPaths,
|
||||
IJsonSerializer jsonSerializer,
|
||||
ILogger<TaskManager> logger,
|
||||
IFileSystem fileSystem)
|
||||
ILogger<TaskManager> logger)
|
||||
{
|
||||
_applicationPaths = applicationPaths;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
ScheduledTasks = Array.Empty<IScheduledTaskWorker>();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
|
||||
namespace Emby.Server.Implementations.ScheduledTasks
|
||||
@@ -24,11 +23,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// </summary>
|
||||
public class ChapterImagesTask : IScheduledTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
private readonly ILogger<ChapterImagesTask> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// The _library manager.
|
||||
/// </summary>
|
||||
@@ -46,7 +40,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
/// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
|
||||
/// </summary>
|
||||
public ChapterImagesTask(
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager,
|
||||
IItemRepository itemRepo,
|
||||
IApplicationPaths appPaths,
|
||||
@@ -54,7 +47,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
IFileSystem fileSystem,
|
||||
ILocalizationManager localization)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ChapterImagesTask>();
|
||||
_libraryManager = libraryManager;
|
||||
_itemRepo = itemRepo;
|
||||
_appPaths = appPaths;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using MediaBrowser.Model.Services;
|
||||
@@ -91,12 +92,22 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
if (restPath.Path[0] != '/')
|
||||
{
|
||||
throw new ArgumentException(string.Format("Route '{0}' on '{1}' must start with a '/'", restPath.Path, restPath.RequestType.GetMethodName()));
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Route '{0}' on '{1}' must start with a '/'",
|
||||
restPath.Path,
|
||||
restPath.RequestType.GetMethodName()));
|
||||
}
|
||||
|
||||
if (restPath.Path.IndexOfAny(InvalidRouteChars) != -1)
|
||||
{
|
||||
throw new ArgumentException(string.Format("Route '{0}' on '{1}' contains invalid chars. ", restPath.Path, restPath.RequestType.GetMethodName()));
|
||||
throw new ArgumentException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Route '{0}' on '{1}' contains invalid chars. ",
|
||||
restPath.Path,
|
||||
restPath.RequestType.GetMethodName()));
|
||||
}
|
||||
|
||||
if (RestPathMap.TryGetValue(restPath.FirstMatchHashKey, out List<RestPath> pathsAtFirstMatch))
|
||||
@@ -179,8 +190,7 @@ namespace Emby.Server.Implementations.Services
|
||||
|
||||
var service = httpHost.CreateInstance(serviceType);
|
||||
|
||||
var serviceRequiresContext = service as IRequiresRequest;
|
||||
if (serviceRequiresContext != null)
|
||||
if (service is IRequiresRequest serviceRequiresContext)
|
||||
{
|
||||
serviceRequiresContext.Request = req;
|
||||
}
|
||||
@@ -189,5 +199,4 @@ namespace Emby.Server.Implementations.Services
|
||||
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
@@ -105,7 +106,13 @@ namespace Emby.Server.Implementations.Services
|
||||
}
|
||||
|
||||
var expectedMethodName = actionName.Substring(0, 1) + actionName.Substring(1).ToLowerInvariant();
|
||||
throw new NotImplementedException(string.Format("Could not find method named {1}({0}) or Any({0}) on Service {2}", requestDto.GetType().GetMethodName(), expectedMethodName, serviceType.GetMethodName()));
|
||||
throw new NotImplementedException(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Could not find method named {1}({0}) or Any({0}) on Service {2}",
|
||||
requestDto.GetType().GetMethodName(),
|
||||
expectedMethodName,
|
||||
serviceType.GetMethodName()));
|
||||
}
|
||||
|
||||
private static async Task<object> GetTaskResult(Task task)
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -44,7 +46,7 @@ namespace Emby.Server.Implementations.Services
|
||||
var pos = pathInfo.LastIndexOf('.');
|
||||
if (pos != -1)
|
||||
{
|
||||
var format = pathInfo.Substring(pos + 1);
|
||||
var format = pathInfo.AsSpan().Slice(pos + 1);
|
||||
contentType = GetFormatContentType(format);
|
||||
if (contentType != null)
|
||||
{
|
||||
@@ -55,18 +57,21 @@ namespace Emby.Server.Implementations.Services
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
private static string GetFormatContentType(string format)
|
||||
private static string GetFormatContentType(ReadOnlySpan<char> format)
|
||||
{
|
||||
// built-in formats
|
||||
switch (format)
|
||||
if (format.Equals("json", StringComparison.Ordinal))
|
||||
{
|
||||
case "json": return "application/json";
|
||||
case "xml": return "application/xml";
|
||||
default: return null;
|
||||
return MediaTypeNames.Application.Json;
|
||||
}
|
||||
else if (format.Equals("xml", StringComparison.Ordinal))
|
||||
{
|
||||
return MediaTypeNames.Application.Xml;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, ILogger logger, CancellationToken cancellationToken)
|
||||
public async Task ProcessRequestAsync(HttpListenerHost httpHost, IRequest httpReq, HttpResponse httpRes, CancellationToken cancellationToken)
|
||||
{
|
||||
httpReq.Items["__route"] = _restPath;
|
||||
|
||||
@@ -75,10 +80,11 @@ namespace Emby.Server.Implementations.Services
|
||||
httpReq.ResponseContentType = _responseContentType;
|
||||
}
|
||||
|
||||
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
|
||||
var request = await CreateRequest(httpHost, httpReq, _restPath).ConfigureAwait(false);
|
||||
|
||||
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
|
||||
|
||||
httpRes.HttpContext.SetServiceStackRequest(httpReq);
|
||||
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
|
||||
|
||||
// Apply response filters
|
||||
@@ -90,7 +96,7 @@ namespace Emby.Server.Implementations.Services
|
||||
await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger)
|
||||
public static async Task<object> CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath)
|
||||
{
|
||||
var requestType = restPath.RequestType;
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
var component = components[i];
|
||||
|
||||
if (component.StartsWith(VariablePrefix))
|
||||
if (component.StartsWith(VariablePrefix, StringComparison.Ordinal))
|
||||
{
|
||||
var variableName = component.Substring(1, component.Length - 2);
|
||||
if (variableName[variableName.Length - 1] == WildCardChar)
|
||||
@@ -488,7 +488,8 @@ namespace Emby.Server.Implementations.Services
|
||||
sb.Append(value);
|
||||
for (var j = pathIx + 1; j < requestComponents.Length; j++)
|
||||
{
|
||||
sb.Append(PathSeperatorChar + requestComponents[j]);
|
||||
sb.Append(PathSeperatorChar)
|
||||
.Append(requestComponents[j]);
|
||||
}
|
||||
|
||||
value = sb.ToString();
|
||||
@@ -505,7 +506,8 @@ namespace Emby.Server.Implementations.Services
|
||||
pathIx++;
|
||||
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
|
||||
sb.Append(PathSeperatorChar)
|
||||
.Append(requestComponents[pathIx++]);
|
||||
}
|
||||
|
||||
value = sb.ToString();
|
||||
|
||||
@@ -848,8 +848,8 @@ namespace Emby.Server.Implementations.Session
|
||||
/// </summary>
|
||||
/// <param name="info">The info.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="ArgumentNullException">info</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">positionTicks</exception>
|
||||
/// <exception cref="ArgumentNullException"><c>info</c> is <c>null</c>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException"><c>info.PositionTicks</c> is <c>null</c> or negative.</exception>
|
||||
public async Task OnPlaybackStopped(PlaybackStopInfo info)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace Emby.Server.Implementations.Session
|
||||
if (session != null)
|
||||
{
|
||||
EnsureController(session, e.Argument);
|
||||
await KeepAliveWebSocket(e.Argument);
|
||||
await KeepAliveWebSocket(e.Argument).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Session
|
||||
// Notify WebSocket about timeout
|
||||
try
|
||||
{
|
||||
await SendForceKeepAlive(webSocket);
|
||||
await SendForceKeepAlive(webSocket).ConfigureAwait(false);
|
||||
}
|
||||
catch (WebSocketException exception)
|
||||
{
|
||||
@@ -233,6 +233,7 @@ namespace Emby.Server.Implementations.Session
|
||||
if (_keepAliveCancellationToken != null)
|
||||
{
|
||||
_keepAliveCancellationToken.Cancel();
|
||||
_keepAliveCancellationToken.Dispose();
|
||||
_keepAliveCancellationToken = null;
|
||||
}
|
||||
}
|
||||
@@ -268,7 +269,7 @@ namespace Emby.Server.Implementations.Session
|
||||
lost = _webSockets.Where(i => (DateTime.UtcNow - i.LastKeepAliveDate).TotalSeconds >= WebSocketLostTimeout).ToList();
|
||||
}
|
||||
|
||||
if (inactive.Any())
|
||||
if (inactive.Count > 0)
|
||||
{
|
||||
_logger.LogInformation("Sending ForceKeepAlive message to {0} inactive WebSockets.", inactive.Count);
|
||||
}
|
||||
@@ -277,7 +278,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
try
|
||||
{
|
||||
await SendForceKeepAlive(webSocket);
|
||||
await SendForceKeepAlive(webSocket).ConfigureAwait(false);
|
||||
}
|
||||
catch (WebSocketException exception)
|
||||
{
|
||||
@@ -288,7 +289,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
lock (_webSocketsLock)
|
||||
{
|
||||
if (lost.Any())
|
||||
if (lost.Count > 0)
|
||||
{
|
||||
_logger.LogInformation("Lost {0} WebSockets.", lost.Count);
|
||||
foreach (var webSocket in lost)
|
||||
@@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
if (!_webSockets.Any())
|
||||
if (_webSockets.Count == 0)
|
||||
{
|
||||
StopKeepAlive();
|
||||
}
|
||||
@@ -312,11 +313,13 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <returns>Task.</returns>
|
||||
private Task SendForceKeepAlive(IWebSocketConnection webSocket)
|
||||
{
|
||||
return webSocket.SendAsync(new WebSocketMessage<int>
|
||||
{
|
||||
MessageType = "ForceKeepAlive",
|
||||
Data = WebSocketLostTimeout
|
||||
}, CancellationToken.None);
|
||||
return webSocket.SendAsync(
|
||||
new WebSocketMessage<int>
|
||||
{
|
||||
MessageType = "ForceKeepAlive",
|
||||
Data = WebSocketLostTimeout
|
||||
},
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -330,12 +333,11 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await callback();
|
||||
Task task = Task.Delay(interval, cancellationToken);
|
||||
await callback().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await task;
|
||||
await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
|
||||
@@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Sorting
|
||||
|
||||
private static int CompareEpisodes(Episode x, Episode y)
|
||||
{
|
||||
var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
|
||||
var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
|
||||
var xValue = ((x.ParentIndexNumber ?? -1) * 1000) + (x.IndexNumber ?? -1);
|
||||
var yValue = ((y.ParentIndexNumber ?? -1) * 1000) + (y.IndexNumber ?? -1);
|
||||
|
||||
return xValue.CompareTo(yValue);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -27,14 +28,17 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
/// All sessions will receive the message.
|
||||
/// </summary>
|
||||
AllGroup = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Only the specified session will receive the message.
|
||||
/// </summary>
|
||||
CurrentSession = 1,
|
||||
|
||||
/// <summary>
|
||||
/// All sessions, except the current one, will receive the message.
|
||||
/// </summary>
|
||||
AllExceptCurrentSession = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Only sessions that are not buffering will receive the message.
|
||||
/// </summary>
|
||||
@@ -56,15 +60,6 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
/// </summary>
|
||||
private readonly GroupInfo _group = new GroupInfo();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid GetGroupId() => _group.GroupId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid GetPlayingItemId() => _group.PlayingItem.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGroupEmpty() => _group.IsEmpty();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncPlayController" /> class.
|
||||
/// </summary>
|
||||
@@ -78,6 +73,15 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
_syncPlayManager = syncPlayManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid GetGroupId() => _group.GroupId;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid GetPlayingItemId() => _group.PlayingItem.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsGroupEmpty() => _group.IsEmpty();
|
||||
|
||||
/// <summary>
|
||||
/// Converts DateTime to UTC string.
|
||||
/// </summary>
|
||||
@@ -85,7 +89,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
/// <value>The UTC string.</value>
|
||||
private string DateToUTCString(DateTime date)
|
||||
{
|
||||
return date.ToUniversalTime().ToString("o");
|
||||
return date.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,23 +98,23 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
/// <param name="from">The current session.</param>
|
||||
/// <param name="type">The filtering type.</param>
|
||||
/// <value>The array of sessions matching the filter.</value>
|
||||
private SessionInfo[] FilterSessions(SessionInfo from, BroadcastType type)
|
||||
private IEnumerable<SessionInfo> FilterSessions(SessionInfo from, BroadcastType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BroadcastType.CurrentSession:
|
||||
return new SessionInfo[] { from };
|
||||
case BroadcastType.AllGroup:
|
||||
return _group.Participants.Values.Select(
|
||||
session => session.Session).ToArray();
|
||||
return _group.Participants.Values
|
||||
.Select(session => session.Session);
|
||||
case BroadcastType.AllExceptCurrentSession:
|
||||
return _group.Participants.Values.Select(
|
||||
session => session.Session).Where(
|
||||
session => !session.Id.Equals(from.Id)).ToArray();
|
||||
return _group.Participants.Values
|
||||
.Select(session => session.Session)
|
||||
.Where(session => !session.Id.Equals(from.Id, StringComparison.Ordinal));
|
||||
case BroadcastType.AllReady:
|
||||
return _group.Participants.Values.Where(
|
||||
session => !session.IsBuffering).Select(
|
||||
session => session.Session).ToArray();
|
||||
return _group.Participants.Values
|
||||
.Where(session => !session.IsBuffering)
|
||||
.Select(session => session.Session);
|
||||
default:
|
||||
return Array.Empty<SessionInfo>();
|
||||
}
|
||||
@@ -128,10 +132,9 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
{
|
||||
IEnumerable<Task> GetTasks()
|
||||
{
|
||||
SessionInfo[] sessions = FilterSessions(from, type);
|
||||
foreach (var session in sessions)
|
||||
foreach (var session in FilterSessions(from, type))
|
||||
{
|
||||
yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id.ToString(), message, cancellationToken);
|
||||
yield return _sessionManager.SendSyncPlayGroupUpdate(session.Id, message, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +153,9 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
{
|
||||
IEnumerable<Task> GetTasks()
|
||||
{
|
||||
SessionInfo[] sessions = FilterSessions(from, type);
|
||||
foreach (var session in sessions)
|
||||
foreach (var session in FilterSessions(from, type))
|
||||
{
|
||||
yield return _sessionManager.SendSyncPlayCommand(session.Id.ToString(), message, cancellationToken);
|
||||
yield return _sessionManager.SendSyncPlayCommand(session.Id, message, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +238,11 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
}
|
||||
else
|
||||
{
|
||||
var playRequest = new PlayRequest();
|
||||
playRequest.ItemIds = new Guid[] { _group.PlayingItem.Id };
|
||||
playRequest.StartPositionTicks = _group.PositionTicks;
|
||||
var playRequest = new PlayRequest
|
||||
{
|
||||
ItemIds = new Guid[] { _group.PlayingItem.Id },
|
||||
StartPositionTicks = _group.PositionTicks
|
||||
};
|
||||
var update = NewSyncPlayGroupUpdate(GroupUpdateType.PrepareSession, playRequest);
|
||||
SendGroupUpdate(session, BroadcastType.CurrentSession, update, cancellationToken);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user