Merge branch 'master' into authenticationdb-efcore

# Conflicts:
#	Emby.Server.Implementations/Devices/DeviceManager.cs
#	Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
#	Emby.Server.Implementations/Security/AuthenticationRepository.cs
#	Emby.Server.Implementations/Session/SessionManager.cs
#	Jellyfin.Server.Implementations/Security/AuthorizationContext.cs
#	MediaBrowser.Controller/Library/IUserManager.cs
#	MediaBrowser.Controller/Net/ISessionContext.cs
This commit is contained in:
Patrick Barron
2021-06-18 18:47:44 -04:00
357 changed files with 3277 additions and 2315 deletions

View File

@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.AppBase
CachePath = cacheDirectoryPath;
WebPath = webDirectoryPath;
DataPath = Path.Combine(ProgramDataPath, "data");
_dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
}
/// <summary>
@@ -55,11 +55,7 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the folder path to the data directory.
/// </summary>
/// <value>The data directory.</value>
public string DataPath
{
get => _dataPath;
private set => _dataPath = Directory.CreateDirectory(value).FullName;
}
public string DataPath => _dataPath;
/// <inheritdoc />
public string VirtualDataPath => "%AppDataPath%";

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -23,6 +25,11 @@ namespace Emby.Server.Implementations.AppBase
private readonly ConcurrentDictionary<string, object> _configurations = new ConcurrentDictionary<string, object>();
/// <summary>
/// The _configuration sync lock.
/// </summary>
private readonly object _configurationSyncLock = new object();
private ConfigurationStore[] _configurationStores = Array.Empty<ConfigurationStore>();
private IConfigurationFactory[] _configurationFactories = Array.Empty<IConfigurationFactory>();
@@ -31,11 +38,6 @@ namespace Emby.Server.Implementations.AppBase
/// </summary>
private bool _configurationLoaded;
/// <summary>
/// The _configuration sync lock.
/// </summary>
private readonly object _configurationSyncLock = new object();
/// <summary>
/// The _configuration.
/// </summary>
@@ -297,25 +299,29 @@ namespace Emby.Server.Implementations.AppBase
/// <inheritdoc />
public object GetConfiguration(string key)
{
return _configurations.GetOrAdd(key, k =>
{
var file = GetConfigurationFile(key);
var configurationInfo = _configurationStores
.FirstOrDefault(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
if (configurationInfo == null)
return _configurations.GetOrAdd(
key,
(k, configurationManager) =>
{
throw new ResourceNotFoundException("Configuration with key " + key + " not found.");
}
var file = configurationManager.GetConfigurationFile(k);
var configurationType = configurationInfo.ConfigurationType;
var configurationInfo = Array.Find(
configurationManager._configurationStores,
i => string.Equals(i.Key, k, StringComparison.OrdinalIgnoreCase));
lock (_configurationSyncLock)
{
return LoadConfiguration(file, configurationType);
}
});
if (configurationInfo == null)
{
throw new ResourceNotFoundException("Configuration with key " + k + " not found.");
}
var configurationType = configurationInfo.ConfigurationType;
lock (configurationManager._configurationSyncLock)
{
return configurationManager.LoadConfiguration(file, configurationType);
}
},
this);
}
private object LoadConfiguration(string path, Type configurationType)

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
@@ -35,7 +33,8 @@ namespace Emby.Server.Implementations.AppBase
}
catch (Exception)
{
configuration = Activator.CreateInstance(type) ?? throw new ArgumentException($"Provided path ({type}) is not valid.", nameof(type));
// Note: CreateInstance returns null for Nullable<T>, e.g. CreateInstance(typeof(int?)) returns null.
configuration = Activator.CreateInstance(type)!;
}
using var stream = new MemoryStream(buffer?.Length ?? 0);

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Globalization;

View File

@@ -82,9 +82,9 @@ namespace Emby.Server.Implementations.Collections
return null;
})
.Where(i => i != null)
.GroupBy(x => x.Id)
.GroupBy(x => x!.Id) // We removed the null values
.Select(x => x.First())
.ToList();
.ToList()!; // Again... the list doesn't contain any null values
}
/// <inheritdoc />

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Globalization;
using System.IO;

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Security.Cryptography;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Data
{
public class ManagedConnection : IDisposable
{
private SQLiteDatabaseConnection _db;
private SQLiteDatabaseConnection? _db;
private readonly SemaphoreSlim _writeLock;
private bool _disposed = false;
@@ -54,12 +54,12 @@ namespace Emby.Server.Implementations.Data
return _db.RunInTransaction(action, mode);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql)
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql)
{
return _db.Query(sql);
}
public IEnumerable<IReadOnlyList<IResultSetValue>> Query(string sql, params object[] values)
public IEnumerable<IReadOnlyList<ResultSetValue>> Query(string sql, params object[] values)
{
return _db.Query(sql, values);
}

View File

@@ -1,9 +1,9 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using SQLitePCL.pretty;
@@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Data
});
}
public static Guid ReadGuidFromBlob(this IResultSetValue result)
public static Guid ReadGuidFromBlob(this ResultSetValue result)
{
return new Guid(result.ToBlob());
}
@@ -86,7 +86,7 @@ namespace Emby.Server.Implementations.Data
private static string GetDateTimeKindFormat(DateTimeKind kind)
=> (kind == DateTimeKind.Utc) ? DatetimeFormatUtc : DatetimeFormatLocal;
public static DateTime ReadDateTime(this IResultSetValue result)
public static DateTime ReadDateTime(this ResultSetValue result)
{
var dateText = result.ToString();
@@ -97,7 +97,7 @@ namespace Emby.Server.Implementations.Data
DateTimeStyles.None).ToUniversalTime();
}
public static bool TryReadDateTime(this IReadOnlyList<IResultSetValue> reader, int index, out DateTime result)
public static bool TryReadDateTime(this IReadOnlyList<ResultSetValue> reader, int index, out DateTime result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
public static bool TryGetGuid(this IReadOnlyList<IResultSetValue> reader, int index, out Guid result)
public static bool TryGetGuid(this IReadOnlyList<ResultSetValue> reader, int index, out Guid result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -131,17 +131,17 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static bool IsDbNull(this IResultSetValue result)
public static bool IsDbNull(this ResultSetValue result)
{
return result.SQLiteType == SQLiteType.Null;
}
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
public static string GetString(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToString();
}
public static bool TryGetString(this IReadOnlyList<IResultSetValue> reader, int index, out string result)
public static bool TryGetString(this IReadOnlyList<ResultSetValue> reader, int index, out string result)
{
result = null;
var item = reader[index];
@@ -154,12 +154,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
public static bool GetBoolean(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToBool();
}
public static bool TryGetBoolean(this IReadOnlyList<IResultSetValue> reader, int index, out bool result)
public static bool TryGetBoolean(this IReadOnlyList<ResultSetValue> reader, int index, out bool result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static bool TryGetInt32(this IReadOnlyList<IResultSetValue> reader, int index, out int result)
public static bool TryGetInt32(this IReadOnlyList<ResultSetValue> reader, int index, out int result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -185,12 +185,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
public static long GetInt64(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ToInt64();
}
public static bool TryGetInt64(this IReadOnlyList<IResultSetValue> reader, int index, out long result)
public static bool TryGetInt64(this IReadOnlyList<ResultSetValue> reader, int index, out long result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -203,7 +203,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static bool TryGetSingle(this IReadOnlyList<IResultSetValue> reader, int index, out float result)
public static bool TryGetSingle(this IReadOnlyList<ResultSetValue> reader, int index, out float result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -216,7 +216,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static bool TryGetDouble(this IReadOnlyList<IResultSetValue> reader, int index, out double result)
public static bool TryGetDouble(this IReadOnlyList<ResultSetValue> reader, int index, out double result)
{
var item = reader[index];
if (item.IsDbNull())
@@ -229,7 +229,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)
public static Guid GetGuid(this IReadOnlyList<ResultSetValue> result, int index)
{
return result[index].ReadGuidFromBlob();
}
@@ -441,7 +441,7 @@ namespace Emby.Server.Implementations.Data
}
}
public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
public static IEnumerable<IReadOnlyList<ResultSetValue>> ExecuteQuery(this IStatement statement)
{
while (statement.MoveNext())
{

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -41,6 +43,7 @@ namespace Emby.Server.Implementations.Data
/// </summary>
public class SqliteItemRepository : BaseSqliteRepository, IItemRepository
{
private const string FromText = " from TypedBaseItems A";
private const string ChaptersTableName = "Chapters2";
private readonly IServerConfigurationManager _config;
@@ -1043,18 +1046,34 @@ namespace Emby.Server.Implementations.Data
return Array.Empty<ItemImageInfo>();
}
var list = new List<ItemImageInfo>();
foreach (var part in value.SpanSplit('|'))
// TODO The following is an ugly performance optimization, but it's extremely unlikely that the data in the database would be malformed
var valueSpan = value.AsSpan();
var count = valueSpan.Count('|') + 1;
var position = 0;
var result = new ItemImageInfo[count];
foreach (var part in valueSpan.Split('|'))
{
var image = ItemImageInfoFromValueString(part);
if (image != null)
{
list.Add(image);
result[position++] = image;
}
}
return list.ToArray();
if (position == count)
{
return result;
}
if (position == 0)
{
return Array.Empty<ItemImageInfo>();
}
// Extremely unlikely, but somehow one or more of the image strings were malformed. Cut the array.
return result[..position];
}
private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
@@ -1282,12 +1301,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query)
private BaseItem GetItem(IReadOnlyList<ResultSetValue> reader, InternalItemsQuery query)
{
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query));
}
private BaseItem GetItem(IReadOnlyList<IResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
private BaseItem GetItem(IReadOnlyList<ResultSetValue> reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields)
{
var typeString = reader.GetString(0);
@@ -1927,7 +1946,7 @@ namespace Emby.Server.Implementations.Data
/// <param name="reader">The reader.</param>
/// <param name="item">The item.</param>
/// <returns>ChapterInfo.</returns>
private ChapterInfo GetChapter(IReadOnlyList<IResultSetValue> reader, BaseItem item)
private ChapterInfo GetChapter(IReadOnlyList<ResultSetValue> reader, BaseItem item)
{
var chapter = new ChapterInfo
{
@@ -2248,10 +2267,8 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
}
private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
private void SetFinalColumnsToSelect(InternalItemsQuery query, List<string> columns)
{
var list = startColumns.ToList();
foreach (var field in _allFields)
{
if (!HasField(query, field))
@@ -2259,28 +2276,28 @@ namespace Emby.Server.Implementations.Data
switch (field)
{
case ItemFields.Settings:
list.Remove("IsLocked");
list.Remove("PreferredMetadataCountryCode");
list.Remove("PreferredMetadataLanguage");
list.Remove("LockedFields");
columns.Remove("IsLocked");
columns.Remove("PreferredMetadataCountryCode");
columns.Remove("PreferredMetadataLanguage");
columns.Remove("LockedFields");
break;
case ItemFields.ServiceName:
list.Remove("ExternalServiceId");
columns.Remove("ExternalServiceId");
break;
case ItemFields.SortName:
list.Remove("ForcedSortName");
columns.Remove("ForcedSortName");
break;
case ItemFields.Taglines:
list.Remove("Tagline");
columns.Remove("Tagline");
break;
case ItemFields.Tags:
list.Remove("Tags");
columns.Remove("Tags");
break;
case ItemFields.IsHD:
// do nothing
break;
default:
list.Remove(field.ToString());
columns.Remove(field.ToString());
break;
}
}
@@ -2288,60 +2305,60 @@ namespace Emby.Server.Implementations.Data
if (!HasProgramAttributes(query))
{
list.Remove("IsMovie");
list.Remove("IsSeries");
list.Remove("EpisodeTitle");
list.Remove("IsRepeat");
list.Remove("ShowId");
columns.Remove("IsMovie");
columns.Remove("IsSeries");
columns.Remove("EpisodeTitle");
columns.Remove("IsRepeat");
columns.Remove("ShowId");
}
if (!HasEpisodeAttributes(query))
{
list.Remove("SeasonName");
list.Remove("SeasonId");
columns.Remove("SeasonName");
columns.Remove("SeasonId");
}
if (!HasStartDate(query))
{
list.Remove("StartDate");
columns.Remove("StartDate");
}
if (!HasTrailerTypes(query))
{
list.Remove("TrailerTypes");
columns.Remove("TrailerTypes");
}
if (!HasArtistFields(query))
{
list.Remove("AlbumArtists");
list.Remove("Artists");
columns.Remove("AlbumArtists");
columns.Remove("Artists");
}
if (!HasSeriesFields(query))
{
list.Remove("SeriesId");
columns.Remove("SeriesId");
}
if (!HasEpisodeAttributes(query))
{
list.Remove("SeasonName");
list.Remove("SeasonId");
columns.Remove("SeasonName");
columns.Remove("SeasonId");
}
if (!query.DtoOptions.EnableImages)
{
list.Remove("Images");
columns.Remove("Images");
}
if (EnableJoinUserData(query))
{
list.Add("UserDatas.UserId");
list.Add("UserDatas.lastPlayedDate");
list.Add("UserDatas.playbackPositionTicks");
list.Add("UserDatas.playcount");
list.Add("UserDatas.isFavorite");
list.Add("UserDatas.played");
list.Add("UserDatas.rating");
columns.Add("UserDatas.UserId");
columns.Add("UserDatas.lastPlayedDate");
columns.Add("UserDatas.playbackPositionTicks");
columns.Add("UserDatas.playcount");
columns.Add("UserDatas.isFavorite");
columns.Add("UserDatas.played");
columns.Add("UserDatas.rating");
}
if (query.SimilarTo != null)
@@ -2389,7 +2406,7 @@ namespace Emby.Server.Implementations.Data
builder.Append(") as SimilarityScore");
list.Add(builder.ToString());
columns.Add(builder.ToString());
var oldLen = query.ExcludeItemIds.Length;
var newLen = oldLen + item.ExtraIds.Length + 1;
@@ -2416,10 +2433,8 @@ namespace Emby.Server.Implementations.Data
builder.Append(") as SearchScore");
list.Add(builder.ToString());
columns.Add(builder.ToString());
}
return list;
}
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@@ -2485,31 +2500,25 @@ namespace Emby.Server.Implementations.Data
private string GetGroupBy(InternalItemsQuery query)
{
var groups = new List<string>();
if (EnableGroupByPresentationUniqueKey(query))
var enableGroupByPresentationUniqueKey = EnableGroupByPresentationUniqueKey(query);
if (enableGroupByPresentationUniqueKey && query.GroupBySeriesPresentationUniqueKey)
{
groups.Add("PresentationUniqueKey");
return " Group by PresentationUniqueKey, SeriesPresentationUniqueKey";
}
if (enableGroupByPresentationUniqueKey)
{
return " Group by PresentationUniqueKey";
}
if (query.GroupBySeriesPresentationUniqueKey)
{
groups.Add("SeriesPresentationUniqueKey");
}
if (groups.Count > 0)
{
return " Group by " + string.Join(',', groups);
return " Group by SeriesPresentationUniqueKey";
}
return string.Empty;
}
private string GetFromText(string alias = "A")
{
return " from TypedBaseItems " + alias;
}
public int GetCount(InternalItemsQuery query)
{
if (query == null)
@@ -2527,17 +2536,21 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query);
var columns = new List<string> { "count(distinct PresentationUniqueKey)" };
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 256)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandText += " where " + string.Join(" AND ", whereClauses);
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
var commandText = commandTextBuilder.ToString();
int count;
using (var connection = GetConnection(true))
{
@@ -2579,20 +2592,23 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
var columns = _retriveItemColumns.ToList();
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 1024)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandText += " where " + string.Join(" AND ", whereClauses);
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
commandText += GetGroupBy(query)
+ GetOrderByText(query);
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -2600,15 +2616,18 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" LIMIT ")
.Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" OFFSET ")
.Append(offset);
}
}
var commandText = commandTextBuilder.ToString();
var items = new List<BaseItem>();
using (var connection = GetConnection(true))
{
@@ -2764,20 +2783,27 @@ namespace Emby.Server.Implementations.Data
query.Limit = query.Limit.Value + 4;
}
var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
var columns = _retriveItemColumns.ToList();
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 512)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
var whereText = whereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", whereClauses);
string.Join(" AND ", whereClauses);
commandText += whereText
+ GetGroupBy(query)
+ GetOrderByText(query);
if (!string.IsNullOrEmpty(whereText))
{
commandTextBuilder.Append(" where ")
.Append(whereText);
}
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -2785,43 +2811,58 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" LIMIT ")
.Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" OFFSET ")
.Append(offset);
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
var statementTexts = new List<string>();
var itemQuery = string.Empty;
var totalRecordCountQuery = string.Empty;
if (!isReturningZeroItems)
{
statementTexts.Add(commandText);
itemQuery = commandTextBuilder.ToString();
}
if (query.EnableTotalRecordCount)
{
commandText = string.Empty;
commandTextBuilder.Clear();
commandTextBuilder.Append(" select ");
List<string> columnsToSelect;
if (EnableGroupByPresentationUniqueKey(query))
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" };
}
else
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
columnsToSelect = new List<string> { "count (guid)" };
}
commandText += GetJoinUserDataText(query)
+ whereText;
statementTexts.Add(commandText);
SetFinalColumnsToSelect(query, columnsToSelect);
commandTextBuilder.AppendJoin(',', columnsToSelect)
.Append(FromText)
.Append(GetJoinUserDataText(query));
if (!string.IsNullOrEmpty(whereText))
{
commandTextBuilder.Append(" where ")
.Append(whereText);
}
totalRecordCountQuery = commandTextBuilder.ToString();
}
var list = new List<BaseItem>();
@@ -2831,11 +2872,12 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts);
var itemQueryStatement = PrepareStatement(db, itemQuery);
var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery);
if (!isReturningZeroItems)
{
using (var statement = statements[0])
using (var statement = itemQueryStatement)
{
if (EnableJoinUserData(query))
{
@@ -2865,11 +2907,14 @@ namespace Emby.Server.Implementations.Data
}
}
}
LogQueryTime("GetItems.ItemQuery", itemQuery, now);
}
now = DateTime.UtcNow;
if (query.EnableTotalRecordCount)
{
using (var statement = statements[statements.Length - 1])
using (var statement = totalRecordCountQueryStatement)
{
if (EnableJoinUserData(query))
{
@@ -2884,11 +2929,12 @@ namespace Emby.Server.Implementations.Data
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
}
LogQueryTime("GetItems.TotalRecordCount", totalRecordCountQuery, now);
}
}, ReadTransactionMode);
}
LogQueryTime("GetItems", commandText, now);
result.Items = list;
return result;
}
@@ -3021,19 +3067,22 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ GetJoinUserDataText(query);
var columns = new List<string> { "guid" };
SetFinalColumnsToSelect(query, columns);
var commandTextBuilder = new StringBuilder("select ", 256)
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query));
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
{
commandText += " where " + string.Join(" AND ", whereClauses);
commandTextBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses);
}
commandText += GetGroupBy(query)
+ GetOrderByText(query);
commandTextBuilder.Append(GetGroupBy(query))
.Append(GetOrderByText(query));
if (query.Limit.HasValue || query.StartIndex.HasValue)
{
@@ -3041,15 +3090,18 @@ namespace Emby.Server.Implementations.Data
if (query.Limit.HasValue || offset > 0)
{
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" LIMIT ")
.Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
commandTextBuilder.Append(" OFFSET ")
.Append(offset);
}
}
var commandText = commandTextBuilder.ToString();
var list = new List<Guid>();
using (var connection = GetConnection(true))
{
@@ -3088,7 +3140,9 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText();
var columns = new List<string> { "guid", "path" };
SetFinalColumnsToSelect(query, columns);
var commandText = "select " + string.Join(',', columns) + FromText;
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
@@ -3164,9 +3218,11 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var columns = new List<string> { "guid" };
SetFinalColumnsToSelect(query, columns);
var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ string.Join(',', columns)
+ FromText
+ GetJoinUserDataText(query);
var whereClauses = GetWhereClauses(query, null);
@@ -3206,19 +3262,23 @@ namespace Emby.Server.Implementations.Data
{
commandText = string.Empty;
List<string> columnsToSelect;
if (EnableGroupByPresentationUniqueKey(query))
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
columnsToSelect = new List<string> { "count (distinct SeriesPresentationUniqueKey)" };
}
else
{
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
columnsToSelect = new List<string> { "count (guid)" };
}
SetFinalColumnsToSelect(query, columnsToSelect);
commandText += " select " + string.Join(',', columnsToSelect) + FromText;
commandText += GetJoinUserDataText(query)
+ whereText;
statementTexts.Add(commandText);
@@ -4413,56 +4473,50 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add(GetProviderIdClause(query.HasTvdbId.Value, "tvdb"));
}
var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
var queryTopParentIds = query.TopParentIds;
if (queryTopParentIds.Length == 1)
if (queryTopParentIds.Length > 0)
{
if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
if (statement != null)
{
statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
}
else
{
whereClauses.Add("(TopParentId=@TopParentId)");
}
var includedItemByNameTypes = GetItemByNameTypesInQuery(query);
var enableItemsByName = (query.IncludeItemsByName ?? false) && includedItemByNameTypes.Count > 0;
if (statement != null)
if (queryTopParentIds.Length == 1)
{
statement.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
}
}
else if (queryTopParentIds.Length > 1)
{
var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
if (statement != null)
if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
statement.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
whereClauses.Add("(TopParentId=@TopParentId or Type=@IncludedItemByNameType)");
statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
}
else
{
whereClauses.Add("(TopParentId=@TopParentId)");
}
statement?.TryBind("@TopParentId", queryTopParentIds[0].ToString("N", CultureInfo.InvariantCulture));
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
else if (queryTopParentIds.Length > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
}
else
{
whereClauses.Add("TopParentId in (" + val + ")");
var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
whereClauses.Add("(Type=@IncludedItemByNameType or TopParentId in (" + val + "))");
statement?.TryBind("@IncludedItemByNameType", includedItemByNameTypes[0]);
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
}
else
{
whereClauses.Add("TopParentId in (" + val + ")");
}
}
}
@@ -4744,17 +4798,12 @@ namespace Emby.Server.Implementations.Data
return true;
}
var types = new[]
{
nameof(Episode),
nameof(Video),
nameof(Movie),
nameof(MusicVideo),
nameof(Series),
nameof(Season)
};
if (types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase)))
if (query.IncludeItemTypes.Contains(nameof(Episode), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Video), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Movie), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(MusicVideo), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Series), StringComparer.OrdinalIgnoreCase)
|| query.IncludeItemTypes.Contains(nameof(Season), StringComparer.OrdinalIgnoreCase))
{
return true;
}
@@ -5198,37 +5247,45 @@ AND Type = @InternalPersonType)");
var now = DateTime.UtcNow;
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
var commandText = "Select Value From ItemValues where " + typeClause;
var stringBuilder = new StringBuilder("Select Value From ItemValues where Type", 128);
if (itemValueTypes.Length == 1)
{
stringBuilder.Append('=')
.Append(itemValueTypes[0]);
}
else
{
stringBuilder.Append(" in (")
.AppendJoin(',', itemValueTypes)
.Append(')');
}
if (withItemTypes.Count > 0)
{
var typeString = string.Join(',', withItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
stringBuilder.Append(" AND ItemId In (select guid from typedbaseitems where type in (")
.AppendJoinInSingleQuotes(',', withItemTypes)
.Append("))");
}
if (excludeItemTypes.Count > 0)
{
var typeString = string.Join(',', excludeItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))";
stringBuilder.Append(" AND ItemId not In (select guid from typedbaseitems where type in (")
.AppendJoinInSingleQuotes(',', excludeItemTypes)
.Append("))");
}
commandText += " Group By CleanValue";
stringBuilder.Append(" Group By CleanValue");
var commandText = stringBuilder.ToString();
var list = new List<string>();
using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText))
{
using (var statement = PrepareStatement(connection, commandText))
foreach (var row in statement.ExecuteQuery())
{
foreach (var row in statement.ExecuteQuery())
if (row.TryGetString(0, out var result))
{
if (row.TryGetString(0, out var result))
{
list.Add(result);
}
list.Add(result);
}
}
}
@@ -5254,18 +5311,19 @@ AND Type = @InternalPersonType)");
var now = DateTime.UtcNow;
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
("Type=" + itemValueTypes[0]) :
("Type in (" + string.Join(',', itemValueTypes) + ")");
InternalItemsQuery typeSubQuery = null;
Dictionary<string, string> itemCountColumns = null;
string itemCountColumns = null;
var stringBuilder = new StringBuilder(1024);
var typesToCount = query.IncludeItemTypes;
if (typesToCount.Length > 0)
{
var itemCountColumnQuery = "select group_concat(type, '|')" + GetFromText("B");
stringBuilder.Append("(select group_concat(type, '|') from TypedBaseItems B");
typeSubQuery = new InternalItemsQuery(query.User)
{
@@ -5281,20 +5339,22 @@ AND Type = @InternalPersonType)");
};
var whereClauses = GetWhereClauses(typeSubQuery, null);
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
stringBuilder.Append(" where ")
.AppendJoin(" AND ", whereClauses)
.Append(" AND ")
.Append("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND ")
.Append(typeClause)
.Append(")) as itemTypes");
itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
itemCountColumns = new Dictionary<string, string>()
{
{ "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes" }
};
itemCountColumns = stringBuilder.ToString();
stringBuilder.Clear();
}
List<string> columns = _retriveItemColumns.ToList();
if (itemCountColumns != null)
// Unfortunately we need to add it to columns to ensure the order of the columns in the select
if (!string.IsNullOrEmpty(itemCountColumns))
{
columns.AddRange(itemCountColumns.Values);
columns.Add(itemCountColumns);
}
// do this first before calling GetFinalColumnsToSelect, otherwise ExcludeItemIds will be set by SimilarTo
@@ -5315,20 +5375,20 @@ AND Type = @InternalPersonType)");
IsSeries = query.IsSeries
};
columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select "
+ string.Join(',', columns)
+ GetFromText()
+ GetJoinUserDataText(query);
SetFinalColumnsToSelect(query, columns);
var innerWhereClauses = GetWhereClauses(innerQuery, null);
var innerWhereText = innerWhereClauses.Count == 0 ?
string.Empty :
" where " + string.Join(" AND ", innerWhereClauses);
stringBuilder.Append(" where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where ")
.Append(typeClause)
.Append(" AND ItemId in (select guid from TypedBaseItems");
if (innerWhereClauses.Count > 0)
{
stringBuilder.Append(" where ")
.AppendJoin(" AND ", innerWhereClauses);
}
var whereText = " where Type=@SelectType And CleanName In (Select CleanValue from ItemValues where " + typeClause + " AND ItemId in (select guid from TypedBaseItems" + innerWhereText + "))";
stringBuilder.Append("))");
var outerQuery = new InternalItemsQuery(query.User)
{
@@ -5353,23 +5413,31 @@ AND Type = @InternalPersonType)");
};
var outerWhereClauses = GetWhereClauses(outerQuery, null);
if (outerWhereClauses.Count != 0)
{
whereText += " AND " + string.Join(" AND ", outerWhereClauses);
stringBuilder.Append(" AND ")
.AppendJoin(" AND ", outerWhereClauses);
}
commandText += whereText + " group by PresentationUniqueKey";
var whereText = stringBuilder.ToString();
stringBuilder.Clear();
stringBuilder.Append("select ")
.AppendJoin(',', columns)
.Append(FromText)
.Append(GetJoinUserDataText(query))
.Append(whereText)
.Append(" group by PresentationUniqueKey");
if (query.OrderBy.Count != 0
|| query.SimilarTo != null
|| !string.IsNullOrEmpty(query.SearchTerm))
{
commandText += GetOrderByText(query);
stringBuilder.Append(GetOrderByText(query));
}
else
{
commandText += " order by SortName";
stringBuilder.Append(" order by SortName");
}
if (query.Limit.HasValue || query.StartIndex.HasValue)
@@ -5378,32 +5446,39 @@ AND Type = @InternalPersonType)");
if (query.Limit.HasValue || offset > 0)
{
commandText += " LIMIT " + (query.Limit ?? int.MaxValue).ToString(CultureInfo.InvariantCulture);
stringBuilder.Append(" LIMIT ")
.Append(query.Limit ?? int.MaxValue);
}
if (offset > 0)
{
commandText += " OFFSET " + offset.ToString(CultureInfo.InvariantCulture);
stringBuilder.Append(" OFFSET ")
.Append(offset);
}
}
var isReturningZeroItems = query.Limit.HasValue && query.Limit <= 0;
var statementTexts = new List<string>();
string commandText = string.Empty;
if (!isReturningZeroItems)
{
statementTexts.Add(commandText);
commandText = stringBuilder.ToString();
}
string countText = string.Empty;
if (query.EnableTotalRecordCount)
{
var countText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
stringBuilder.Clear();
var columnsToSelect = new List<string> { "count (distinct PresentationUniqueKey)" };
SetFinalColumnsToSelect(query, columnsToSelect);
stringBuilder.Append("select ")
.AppendJoin(',', columnsToSelect)
.Append(FromText)
.Append(GetJoinUserDataText(query))
.Append(whereText);
statementTexts.Add(countText);
countText = stringBuilder.ToString();
}
var list = new List<(BaseItem, ItemCounts)>();
@@ -5413,11 +5488,9 @@ AND Type = @InternalPersonType)");
connection.RunInTransaction(
db =>
{
var statements = PrepareAll(db, statementTexts);
if (!isReturningZeroItems)
{
using (var statement = statements[0])
using (var statement = PrepareStatement(db, commandText))
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
@@ -5458,13 +5531,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
using (var statement = statements[statements.Length - 1])
using (var statement = PrepareStatement(db, countText))
{
statement.TryBind("@SelectType", returnType);
if (EnableJoinUserData(query))
@@ -5501,7 +5568,7 @@ AND Type = @InternalPersonType)");
return result;
}
private static ItemCounts GetItemCounts(IReadOnlyList<IResultSetValue> reader, int countStartColumn, string[] typesToCount)
private static ItemCounts GetItemCounts(IReadOnlyList<ResultSetValue> reader, int countStartColumn, string[] typesToCount)
{
var counts = new ItemCounts();
@@ -5732,7 +5799,7 @@ AND Type = @InternalPersonType)");
}
}
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
private PersonInfo GetPerson(IReadOnlyList<ResultSetValue> reader)
{
var item = new PersonInfo
{
@@ -5939,7 +6006,7 @@ AND Type = @InternalPersonType)");
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>ChapterInfo.</returns>
private MediaStream GetMediaStream(IReadOnlyList<IResultSetValue> reader)
private MediaStream GetMediaStream(IReadOnlyList<ResultSetValue> reader)
{
var item = new MediaStream
{
@@ -6240,7 +6307,7 @@ AND Type = @InternalPersonType)");
/// </summary>
/// <param name="reader">The reader.</param>
/// <returns>MediaAttachment.</returns>
private MediaAttachment GetMediaAttachment(IReadOnlyList<IResultSetValue> reader)
private MediaAttachment GetMediaAttachment(IReadOnlyList<ResultSetValue> reader)
{
var item = new MediaAttachment
{

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -348,7 +350,7 @@ namespace Emby.Server.Implementations.Data
/// Read a row from the specified reader into the provided userData object.
/// </summary>
/// <param name="reader"></param>
private UserItemData ReadRow(IReadOnlyList<IResultSetValue> reader)
private UserItemData ReadRow(IReadOnlyList<ResultSetValue> reader)
{
var userData = new UserItemData();

View File

@@ -13,7 +13,7 @@ namespace Emby.Server.Implementations.Data
/// This holds all the types in the running assemblies
/// so that we can de-serialize properly when we don't have strong types.
/// </summary>
private readonly ConcurrentDictionary<string, Type> _typeMap = new ConcurrentDictionary<string, Type>();
private readonly ConcurrentDictionary<string, Type?> _typeMap = new ConcurrentDictionary<string, Type?>();
/// <summary>
/// Gets the type.
@@ -21,26 +21,16 @@ namespace Emby.Server.Implementations.Data
/// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns>
/// <exception cref="ArgumentNullException"><c>typeName</c> is null.</exception>
public Type GetType(string typeName)
public Type? GetType(string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
throw new ArgumentNullException(nameof(typeName));
}
return _typeMap.GetOrAdd(typeName, LookupType);
}
/// <summary>
/// Lookups the type.
/// </summary>
/// <param name="typeName">Name of the type.</param>
/// <returns>Type.</returns>
private Type LookupType(string typeName)
{
return AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(typeName))
.FirstOrDefault(t => t != null);
return _typeMap.GetOrAdd(typeName, k => AppDomain.CurrentDomain.GetAssemblies()
.Select(a => a.GetType(k))
.FirstOrDefault(t => t != null));
}
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -9,6 +9,7 @@
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
<ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -27,11 +28,11 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.7" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
<PackageReference Include="sharpcompress" Version="0.28.2" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.2.0" />
<PackageReference Include="sharpcompress" Version="0.28.3" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup>
@@ -44,6 +45,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
<NoWarn>AD0001</NoWarn>
<AnalysisMode Condition=" '$(Configuration)' == 'Debug' ">AllEnabledByDefault</AnalysisMode>

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -106,8 +108,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.StartDiscovery();
_timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
}
private void Stop()
@@ -118,13 +118,6 @@ namespace Emby.Server.Implementations.EntryPoints
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
_timer?.Dispose();
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Net.Sockets;
using System.Threading;
@@ -56,8 +54,8 @@ namespace Emby.Server.Implementations.EntryPoints
try
{
_udpServer = new UdpServer(_logger, _appHost, _config);
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
_udpServer = new UdpServer(_logger, _appHost, _config, PortNumber);
_udpServer.Start(_cancellationTokenSource.Token);
}
catch (SocketException ex)
{

View File

@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly Dictionary<Guid, List<BaseItem>> _changedItems = new Dictionary<Guid, List<BaseItem>>();
private readonly object _syncLock = new object();
private Timer _updateTimer;
private Timer? _updateTimer;
public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, IUserManager userManager)
{
@@ -42,7 +42,7 @@ namespace Emby.Server.Implementations.EntryPoints
return Task.CompletedTask;
}
void OnUserDataManagerUserDataSaved(object sender, UserDataSaveEventArgs e)
private void OnUserDataManagerUserDataSaved(object? sender, UserDataSaveEventArgs e)
{
if (e.SaveReason == UserDataSaveReason.PlaybackProgress)
{
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.EntryPoints
_updateTimer.Change(UpdateDuration, Timeout.Infinite);
}
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem> keys))
if (!_changedItems.TryGetValue(e.UserId, out List<BaseItem>? keys))
{
keys = new List<BaseItem>();
_changedItems[e.UserId] = keys;
@@ -87,7 +87,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
private void UpdateTimerCallback(object state)
private void UpdateTimerCallback(object? state)
{
lock (_syncLock)
{

View File

@@ -43,14 +43,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
return GetSession((HttpContext)requestContext);
}
public async Task<User> GetUser(HttpContext requestContext)
public async Task<User?> GetUser(HttpContext requestContext)
{
var session = await GetSession(requestContext).ConfigureAwait(false);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
public Task<User> GetUser(object requestContext)
public Task<User?> GetUser(object requestContext)
{
return GetUser(((HttpRequest)requestContext).HttpContext);
}

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Buffers;
using System.IO.Pipelines;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -7,6 +7,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
@@ -61,7 +62,7 @@ namespace Emby.Server.Implementations.IO
/// <param name="filename">The filename.</param>
/// <returns>System.String.</returns>
/// <exception cref="ArgumentNullException">filename</exception>
public virtual string ResolveShortcut(string filename)
public virtual string? ResolveShortcut(string filename)
{
if (string.IsNullOrEmpty(filename))
{
@@ -243,8 +244,8 @@ namespace Emby.Server.Implementations.IO
{
result.Length = fileInfo.Length;
// Issue #2354 get the size of files behind symbolic links
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
// Issue #2354 get the size of files behind symbolic links. Also Enum.HasFlag is bad as it boxes!
if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
try
{
@@ -601,7 +602,7 @@ namespace Emby.Server.Implementations.IO
return GetFiles(path, null, false, recursive);
}
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@@ -618,13 +619,13 @@ namespace Emby.Server.Implementations.IO
{
files = files.Where(i =>
{
var ext = i.Extension;
if (ext == null)
var ext = i.Extension.AsSpan();
if (ext.IsEmpty)
{
return false;
}
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
});
}
@@ -636,8 +637,7 @@ namespace Emby.Server.Implementations.IO
var directoryInfo = new DirectoryInfo(path);
var enumerationOptions = GetEnumerationOptions(recursive);
return ToMetadata(directoryInfo.EnumerateDirectories("*", enumerationOptions))
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", enumerationOptions)));
return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", enumerationOptions));
}
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
@@ -655,7 +655,7 @@ namespace Emby.Server.Implementations.IO
return GetFilePaths(path, null, false, recursive);
}
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
public virtual IEnumerable<string> GetFilePaths(string path, string[]? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var enumerationOptions = GetEnumerationOptions(recursive);
@@ -672,13 +672,13 @@ namespace Emby.Server.Implementations.IO
{
files = files.Where(i =>
{
var ext = Path.GetExtension(i);
if (ext == null)
var ext = Path.GetExtension(i.AsSpan());
if (ext.IsEmpty)
{
return false;
}
return extensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
return extensions.Contains(ext, StringComparison.OrdinalIgnoreCase);
});
}

View File

@@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.IO
public string Extension => ".mblink";
public string Resolve(string shortcutPath)
public string? Resolve(string shortcutPath)
{
if (string.IsNullOrEmpty(shortcutPath))
{

View File

@@ -11,7 +11,7 @@ namespace Emby.Server.Implementations.IO
{
public class StreamHelper : IStreamHelper
{
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action? onStarted, CancellationToken cancellationToken)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591
#nullable enable
namespace Emby.Server.Implementations
{

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;

View File

@@ -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, StringComparison.Ordinal)
if (Path.GetFileNameWithoutExtension(filename.AsSpan()).Equals(BaseItem.ThemeSongFilename, StringComparison.Ordinal)
&& _libraryManager.IsAudioFile(filename))
{
return true;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Linq;
using DotNet.Globbing;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -694,25 +696,32 @@ namespace Emby.Server.Implementations.Library
}
private IEnumerable<BaseItem> ResolveFileList(
IEnumerable<FileSystemMetadata> fileList,
IReadOnlyList<FileSystemMetadata> fileList,
IDirectoryService directoryService,
Folder parent,
string collectionType,
IItemResolver[] resolvers,
LibraryOptions libraryOptions)
{
return fileList.Select(f =>
// Given that fileList is a list we can save enumerator allocations by indexing
for (var i = 0; i < fileList.Count; i++)
{
var file = fileList[i];
BaseItem result = null;
try
{
return ResolvePath(f, directoryService, resolvers, parent, collectionType, libraryOptions);
result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error resolving path {path}", f.FullName);
return null;
_logger.LogError(ex, "Error resolving path {Path}", file.FullName);
}
}).Where(i => i != null);
if (result != null)
{
yield return result;
}
}
}
/// <summary>
@@ -1063,17 +1072,17 @@ namespace Emby.Server.Implementations.Library
// Start by just validating the children of the root, but go no further
await RootFolder.ValidateChildren(
new SimpleProgress<double>(),
cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false).ConfigureAwait(false);
recursive: false,
cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().ValidateChildren(
new SimpleProgress<double>(),
cancellationToken,
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false).ConfigureAwait(false);
recursive: false,
cancellationToken).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes
foreach (var folder in GetUserRootFolder().Children.OfType<Folder>())
@@ -1093,7 +1102,7 @@ namespace Emby.Server.Implementations.Library
innerProgress.RegisterAction(pct => progress.Report(pct * 0.96));
// Validate the entire media library
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken).ConfigureAwait(false);
progress.Report(96);
@@ -2074,7 +2083,7 @@ namespace Emby.Server.Implementations.Library
return new List<Folder>();
}
return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>().ToList());
return GetCollectionFoldersInternal(item, GetUserRootFolder().Children.OfType<Folder>());
}
public List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren)
@@ -2099,10 +2108,10 @@ namespace Emby.Server.Implementations.Library
return GetCollectionFoldersInternal(item, allUserRootChildren);
}
private static List<Folder> GetCollectionFoldersInternal(BaseItem item, List<Folder> allUserRootChildren)
private static List<Folder> GetCollectionFoldersInternal(BaseItem item, IEnumerable<Folder> allUserRootChildren)
{
return allUserRootChildren
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path, StringComparer.OrdinalIgnoreCase))
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path.AsSpan(), StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -2110,9 +2119,9 @@ namespace Emby.Server.Implementations.Library
{
if (!(item is CollectionFolder collectionFolder))
{
// List.Find is more performant than FirstOrDefault due to enumerator allocation
collectionFolder = GetCollectionFolders(item)
.OfType<CollectionFolder>()
.FirstOrDefault();
.Find(folder => folder is CollectionFolder) as CollectionFolder;
}
return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
@@ -2498,8 +2507,7 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public bool IsVideoFile(string path)
{
var resolver = new VideoResolver(GetNamingOptions());
return resolver.IsVideoFile(path);
return VideoResolver.IsVideoFile(path, GetNamingOptions());
}
/// <inheritdoc />
@@ -2677,6 +2685,7 @@ namespace Emby.Server.Implementations.Library
return changed;
}
/// <inheritdoc />
public NamingOptions GetNamingOptions()
{
if (_namingOptions == null)
@@ -2690,13 +2699,12 @@ namespace Emby.Server.Implementations.Library
public ItemLookupInfo ParseName(string name)
{
var resolver = new VideoResolver(GetNamingOptions());
var result = resolver.CleanDateTime(name);
var namingOptions = GetNamingOptions();
var result = VideoResolver.CleanDateTime(name, namingOptions);
return new ItemLookupInfo
{
Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
@@ -2710,9 +2718,7 @@ namespace Emby.Server.Implementations.Library
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
var videoListResolver = new VideoListResolver(namingOptions);
var videos = videoListResolver.Resolve(fileSystemChildren);
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
@@ -2756,9 +2762,7 @@ namespace Emby.Server.Implementations.Library
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
.ToList();
var videoListResolver = new VideoListResolver(namingOptions);
var videos = videoListResolver.Resolve(fileSystemChildren);
var videos = VideoListResolver.Resolve(fileSystemChildren, namingOptions);
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -350,7 +352,7 @@ namespace Emby.Server.Implementations.Library
private string[] NormalizeLanguage(string language)
{
if (language == null)
if (string.IsNullOrEmpty(language))
{
return Array.Empty<string>();
}
@@ -379,8 +381,7 @@ namespace Emby.Server.Implementations.Library
}
}
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
@@ -409,9 +410,7 @@ namespace Emby.Server.Implementations.Library
}
}
var preferredAudio = string.IsNullOrEmpty(user.AudioLanguagePreference)
? Array.Empty<string>()
: NormalizeLanguage(user.AudioLanguagePreference);
var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using MediaBrowser.Common.Providers;

View File

@@ -1,5 +1,3 @@
#nullable enable
using System;
using System.IO;
using System.Linq;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Linq;
using System.Threading.Tasks;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -45,11 +47,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected virtual TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var namingOptions = LibraryManager.GetNamingOptions();
// If the path is a file check for a matching extensions
var parser = new VideoResolver(namingOptions);
if (args.IsDirectory)
{
TVideoType video = null;
@@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
if (IsDvdDirectory(child.FullName, filename, args.DirectoryService))
{
videoInfo = parser.ResolveDirectory(args.Path);
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -82,7 +82,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
if (IsBluRayDirectory(child.FullName, filename, args.DirectoryService))
{
videoInfo = parser.ResolveDirectory(args.Path);
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -100,7 +100,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else if (IsDvdFile(filename))
{
videoInfo = parser.ResolveDirectory(args.Path);
videoInfo = VideoResolver.ResolveDirectory(args.Path, namingOptions);
if (videoInfo == null)
{
@@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
else
{
var videoInfo = parser.Resolve(args.Path, false, false);
var videoInfo = VideoResolver.Resolve(args.Path, false, namingOptions, false);
if (videoInfo == null)
{
@@ -250,10 +250,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void Set3DFormat(Video video)
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var resolver = new Format3DParser(namingOptions);
var result = resolver.Parse(video.Path);
var result = Format3DParser.Parse(video.Path, LibraryManager.GetNamingOptions());
Set3DFormat(video, result.Is3D, result.Format3D);
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;

View File

@@ -1,3 +1,5 @@
#nullable disable
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.IO;
using MediaBrowser.Controller.Entities;

View File

@@ -1,9 +1,12 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Video;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -255,10 +258,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var namingOptions = LibraryManager.GetNamingOptions();
var resolver = new VideoListResolver(namingOptions);
var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
var resolverResult = VideoListResolver.Resolve(files, namingOptions, suppportMultiEditions).ToList();
var result = new MultiItemResolverResult
{
@@ -535,7 +537,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return returnVideo;
}
private bool IsInvalid(Folder parent, string collectionType)
private bool IsInvalid(Folder parent, ReadOnlySpan<char> collectionType)
{
if (parent != null)
{
@@ -545,12 +547,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
}
}
if (string.IsNullOrEmpty(collectionType))
if (collectionType.IsEmpty)
{
return false;
}
return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
return !_validCollectionTypes.Contains(collectionType, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
using System.Linq;
using MediaBrowser.Controller.Entities;

View File

@@ -1,3 +1,5 @@
#nullable disable
using System.Globalization;
using Emby.Naming.TV;
using MediaBrowser.Controller.Entities.TV;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using MediaBrowser.Controller.Entities;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -220,7 +222,7 @@ namespace Emby.Server.Implementations.Library
var hasRuntime = runtimeTicks > 0;
// If a position has been reported, and if we know the duration
if (positionTicks > 0 && hasRuntime && !(item is AudioBook))
if (positionTicks > 0 && hasRuntime && item is not AudioBook && item is not Book)
{
var pctIn = decimal.Divide(positionTicks, runtimeTicks) * 100;
@@ -239,7 +241,7 @@ namespace Emby.Server.Implementations.Library
{
// Enforce MinResumeDuration
var durationSeconds = TimeSpan.FromTicks(runtimeTicks).TotalSeconds;
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds && !(item is Book))
if (durationSeconds < _config.Configuration.MinResumeDurationSeconds)
{
positionTicks = 0;
data.Played = playedToCompletion = true;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -6,7 +6,6 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
internal class EpgChannelData
{
@@ -39,13 +38,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
public ChannelInfo GetChannelById(string id)
public ChannelInfo? GetChannelById(string id)
=> _channelsById.GetValueOrDefault(id);
public ChannelInfo GetChannelByNumber(string number)
public ChannelInfo? GetChannelByNumber(string number)
=> _channelsByNumber.GetValueOrDefault(number);
public ChannelInfo GetChannelByName(string name)
public ChannelInfo? GetChannelByName(string name)
=> _channelsByName.GetValueOrDefault(name);
public static string NormalizeName(string value)

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,21 +1,23 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.LiveTv;
namespace Emby.Server.Implementations.LiveTv
{
/// <summary>
/// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />.
/// </summary>
public class LiveTvConfigurationFactory : IConfigurationFactory
{
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
ConfigurationType = typeof(LiveTvOptions),
Key = "livetv"
ConfigurationType = typeof(LiveTvOptions),
Key = "livetv"
}
};
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -2264,7 +2266,7 @@ namespace Emby.Server.Implementations.LiveTv
if (dataSourceChanged)
{
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
}
return info;
@@ -2307,7 +2309,7 @@ namespace Emby.Server.Implementations.LiveTv
_config.SaveConfiguration("livetv", config);
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
return info;
}
@@ -2319,7 +2321,7 @@ namespace Emby.Server.Implementations.LiveTv
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(id, i.Id, StringComparison.OrdinalIgnoreCase)).ToArray();
_config.SaveConfiguration("livetv", config);
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
}
public async Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelId, string providerChannelId)
@@ -2353,7 +2355,7 @@ namespace Emby.Server.Implementations.LiveTv
var tunerChannelMappings =
tunerChannels.Select(i => GetTunerChannelMapping(i, mappings, providerChannels)).ToList();
_taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
_taskManager.CancelIfRunningAndQueue<RefreshGuideScheduledTask>();
return tunerChannelMappings.First(i => string.Equals(i.Id, tunerChannelId, StringComparison.OrdinalIgnoreCase));
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,7 +1,6 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.LiveTv;
@@ -10,34 +9,55 @@ using MediaBrowser.Model.Tasks;
namespace Emby.Server.Implementations.LiveTv
{
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
/// <summary>
/// The "Refresh Guide" scheduled task.
/// </summary>
public class RefreshGuideScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILiveTvManager _liveTvManager;
private readonly IConfigurationManager _config;
public RefreshChannelsScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
/// <summary>
/// Initializes a new instance of the <see cref="RefreshGuideScheduledTask"/> class.
/// </summary>
/// <param name="liveTvManager">The live tv manager.</param>
/// <param name="config">The configuration manager.</param>
public RefreshGuideScheduledTask(ILiveTvManager liveTvManager, IConfigurationManager config)
{
_liveTvManager = liveTvManager;
_config = config;
}
/// <inheritdoc />
public string Name => "Refresh Guide";
/// <inheritdoc />
public string Description => "Downloads channel information from live tv services.";
/// <inheritdoc />
public string Category => "Live TV";
public Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
/// <inheritdoc />
public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
/// <inheritdoc />
public bool IsEnabled => true;
/// <inheritdoc />
public bool IsLogged => true;
/// <inheritdoc />
public string Key => "RefreshGuide";
/// <inheritdoc />
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
var manager = (LiveTvManager)_liveTvManager;
return manager.RefreshChannels(progress, cancellationToken);
}
/// <summary>
/// Creates the triggers that define when the task will run.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
@@ -51,13 +71,5 @@ namespace Emby.Server.Implementations.LiveTv
{
return _config.GetConfiguration<LiveTvOptions>("livetv");
}
public bool IsHidden => _liveTvManager.Services.Count == 1 && GetConfiguration().TunerHosts.Length == 0;
public bool IsEnabled => true;
public bool IsLogged => true;
public string Key => "RefreshGuide";
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -38,6 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public virtual bool IsSupported => true;
protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
public abstract string Type { get; }
public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)

View File

@@ -1,3 +1,5 @@
#nullable disable
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
internal class Channels

View File

@@ -1,3 +1,5 @@
#nullable disable
using System;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -581,7 +583,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger,
Config,
_appHost,
_networkManager,
_streamHelper);
}
@@ -622,7 +623,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger,
Config,
_appHost,
_networkManager,
_streamHelper);
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -10,7 +12,6 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto;
@@ -28,7 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
public HdHomerunUdpStream(
MediaSourceInfo mediaSource,
@@ -40,12 +40,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
ILogger logger,
IConfigurationManager configurationManager,
IServerApplicationHost appHost,
INetworkManager networkManager,
IStreamHelper streamHelper)
: base(mediaSource, tunerHostInfo, fileSystem, logger, configurationManager, streamHelper)
{
_appHost = appHost;
_networkManager = networkManager;
OriginalStreamId = originalStreamId;
_channelCommands = channelCommands;
_numTuners = numTuners;
@@ -126,7 +124,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (udpClient)
using (hdHomerunManager)
{
if (!(ex is OperationCanceledException))
if (ex is not OperationCanceledException)
{
Logger.LogError(ex, "Error opening live stream:");
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -27,6 +29,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private static readonly string[] _disallowedSharedStreamExtensions =
{
".mkv",
".mp4",
".m3u8",
".mpd"
};
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
private readonly INetworkManager _networkManager;
@@ -65,7 +75,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
return await new M3uParser(Logger, _httpClientFactory, _appHost)
return await new M3uParser(Logger, _httpClientFactory)
.Parse(info, channelIdPrefix, cancellationToken)
.ConfigureAwait(false);
}
@@ -86,14 +96,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
private static readonly string[] _disallowedSharedStreamExtensions =
{
".mkv",
".mp4",
".m3u8",
".mpd"
};
protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = info.TunerCount;
@@ -128,7 +130,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
using (var stream = await new M3uParser(Logger, _httpClientFactory, _appHost).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
using (var stream = await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
{
}
}

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -19,15 +21,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3uParser
{
private const string ExtInfPrefix = "#EXTINF:";
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IServerApplicationHost _appHost;
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory, IServerApplicationHost appHost)
public M3uParser(ILogger logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_appHost = appHost;
}
public async Task<List<ChannelInfo>> Parse(TunerHostInfo info, string channelIdPrefix, CancellationToken cancellationToken)
@@ -59,8 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return File.OpenRead(info.Url);
}
private const string ExtInfPrefix = "#EXTINF:";
private async Task<List<ChannelInfo>> GetChannelsAsync(TextReader reader, string channelIdPrefix, string tunerHostId)
{
var channels = new List<ChannelInfo>();

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;
@@ -89,8 +91,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var taskCompletionSource = new TaskCompletionSource<bool>();
var now = DateTime.UtcNow;
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
// OpenedMediaSource.Protocol = MediaProtocol.File;
@@ -118,7 +118,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!taskCompletionSource.Task.Result)
{
Logger.LogWarning("Zero bytes copied from stream {0} to {1} but no exception raised", GetType().Name, TempFilePath);
throw new EndOfStreamException(String.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
throw new EndOfStreamException(string.Format(CultureInfo.InvariantCulture, "Zero bytes copied from stream {0}", GetType().Name));
}
}

View File

@@ -116,5 +116,7 @@
"TaskCleanLogs": "حذف دليل السجل",
"TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الموضوع.",
"TaskCleanActivityLog": "حذف سجل الأنشطة",
"Default": "الإعدادات الافتراضية"
"Default": "الإعدادات الافتراضية",
"Undefined": "غير معرف",
"Forced": "ملحقة"
}

View File

@@ -117,5 +117,7 @@
"TaskRefreshChannels": "Refresh Channels",
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
"TaskDownloadMissingSubtitles": "Download missing subtitles",
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration."
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
"TaskOptimizeDatabase": "Optimize database",
"TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance."
}

View File

@@ -118,5 +118,7 @@
"TaskCleanActivityLog": "Limpiar registro de actividad",
"Undefined": "Indefinido",
"Forced": "Forzado",
"Default": "Predeterminado"
"Default": "Predeterminado",
"TaskOptimizeDatabase": "Optimizar la base de datos",
"TaskOptimizeDatabaseDescription": "Compacta y libera el espacio libre en la base de datos. Ejecutar esta tarea tras escanear la biblioteca o hacer cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento."
}

View File

@@ -29,9 +29,9 @@
"Collections": "Samlingar",
"ChapterNameValue": "Kapittel {0}",
"Channels": "Kanalar",
"CameraImageUploadedFrom": "Eit nytt kamera bilete har blitt lasta opp frå {0}",
"CameraImageUploadedFrom": "Eit nytt kamerabilete har blitt lasta opp frå {0}",
"Books": "Bøker",
"AuthenticationSucceededWithUserName": "{0} Har logga inn",
"AuthenticationSucceededWithUserName": "{0} har logga inn",
"Artists": "Artistar",
"Application": "Program",
"AppDeviceValues": "App: {0}, Eining: {1}",

View File

@@ -1,9 +1,10 @@
#nullable disable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;
@@ -167,12 +168,22 @@ namespace Emby.Server.Implementations.Localization
/// <inheritdoc />
public CultureDto FindLanguageInfo(string language)
=> GetCultures()
.FirstOrDefault(i =>
string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase)
|| string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase)
|| i.ThreeLetterISOLanguageNames.Contains(language, StringComparer.OrdinalIgnoreCase)
|| string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
{
// TODO language should ideally be a ReadOnlySpan but moq cannot mock ref structs
for (var i = 0; i < _cultures.Count; i++)
{
var culture = _cultures[i];
if (language.Equals(culture.DisplayName, StringComparison.OrdinalIgnoreCase)
|| language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)
|| culture.ThreeLetterISOLanguageNames.Contains(language, StringComparison.OrdinalIgnoreCase)
|| language.Equals(culture.TwoLetterISOLanguageName, StringComparison.OrdinalIgnoreCase))
{
return culture;
}
}
return default;
}
/// <inheritdoc />
public IEnumerable<CountryInfo> GetCountries()
@@ -222,7 +233,7 @@ namespace Emby.Server.Implementations.Localization
throw new ArgumentNullException(nameof(rating));
}
if (_unratedValues.Contains(rating, StringComparer.OrdinalIgnoreCase))
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return null;
}
@@ -250,11 +261,11 @@ namespace Emby.Server.Implementations.Localization
var index = rating.IndexOf(':', StringComparison.Ordinal);
if (index != -1)
{
rating = rating.Substring(index).TrimStart(':').Trim();
var trimmedRating = rating.AsSpan(index).TrimStart(':').Trim();
if (!string.IsNullOrWhiteSpace(rating))
if (!trimmedRating.IsEmpty)
{
return GetRatingLevel(rating);
return GetRatingLevel(trimmedRating.ToString());
}
}
@@ -316,7 +327,8 @@ namespace Emby.Server.Implementations.Localization
return _dictionaries.GetOrAdd(
culture,
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
(key, localizationManager) => localizationManager.GetDictionary(Prefix, key, DefaultCulture + ".json").GetAwaiter().GetResult(),
this);
}
private async Task<Dictionary<string, string>> GetDictionary(string prefix, string culture, string baseFilename)

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

View File

@@ -1,3 +1,5 @@
#nullable disable
#pragma warning disable CS1591
using System;

Some files were not shown because too many files have changed in this diff Show More