aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs10
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs6
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs90
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs2
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs28
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj2
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs7
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs52
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs39
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs23
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs19
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json29
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json19
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json30
-rw-r--r--Emby.Server.Implementations/Localization/Core/eo.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_419.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json7
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json148
-rw-r--r--Emby.Server.Implementations/Localization/Core/fil.json103
-rw-r--r--Emby.Server.Implementations/Localization/Core/gl.json52
-rw-r--r--Emby.Server.Implementations/Localization/Core/hi.json29
-rw-r--r--Emby.Server.Implementations/Localization/Core/id.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/kk.json220
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json12
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/tr.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/vi.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-HK.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json2
-rw-r--r--Emby.Server.Implementations/Localization/iso6392.txt2
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs2
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs157
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs27
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs9
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs7
-rw-r--r--Emby.Server.Implementations/Session/WebSocketController.cs1
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs23
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs22
57 files changed, 755 insertions, 531 deletions
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 1b9bb86bb..0a7c5c1fb 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -374,7 +374,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// Creates an instance of type and resolves all constructor dependencies.
/// </summary>
- /// /// <typeparam name="T">The type.</typeparam>
+ /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns>
public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider);
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 2d5b19fa6..8c5fa09f6 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -336,19 +336,19 @@ namespace Emby.Server.Implementations.Channels
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
}
- private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
+ private MediaSourceInfo[] GetSavedMediaSources(BaseItem item)
{
var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json");
try
{
- var jsonString = File.ReadAllText(path, Encoding.UTF8);
- return JsonSerializer.Deserialize<List<MediaSourceInfo>>(jsonString, _jsonOptions)
- ?? new List<MediaSourceInfo>();
+ var bytes = File.ReadAllBytes(path);
+ return JsonSerializer.Deserialize<MediaSourceInfo[]>(bytes, _jsonOptions)
+ ?? Array.Empty<MediaSourceInfo>();
}
catch
{
- return new List<MediaSourceInfo>();
+ return Array.Empty<MediaSourceInfo>();
}
}
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 3011a37e3..1ab2bdfbe 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Collections
var name = _localizationManager.GetLocalizedString("Collections");
- await _libraryManager.AddVirtualFolder(name, CollectionType.BoxSets, libraryOptions, true).ConfigureAwait(false);
+ await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false);
return FindFolders(path).First();
}
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index 1af301ceb..a04d63088 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using SQLitePCL.pretty;
@@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(conn =>
{
- conn.ExecuteAll(string.Join(";", queries));
+ conn.ExecuteAll(string.Join(';', queries));
});
}
@@ -142,11 +143,10 @@ namespace Emby.Server.Implementations.Data
return result[index].ReadGuidFromBlob();
}
+ [Conditional("DEBUG")]
private static void CheckName(string name)
{
-#if DEBUG
throw new ArgumentException("Invalid param name: " + name, nameof(name));
-#endif
}
public static void TryBind(this IStatement statement, string name, double value)
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 6e1f2feae..d78b93bd7 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -687,7 +687,7 @@ namespace Emby.Server.Implementations.Data
if (item.Genres.Length > 0)
{
- saveItemStatement.TryBind("@Genres", string.Join("|", item.Genres));
+ saveItemStatement.TryBind("@Genres", string.Join('|', item.Genres));
}
else
{
@@ -749,7 +749,7 @@ namespace Emby.Server.Implementations.Data
if (item.LockedFields.Length > 0)
{
- saveItemStatement.TryBind("@LockedFields", string.Join("|", item.LockedFields));
+ saveItemStatement.TryBind("@LockedFields", string.Join('|', item.LockedFields));
}
else
{
@@ -758,7 +758,7 @@ namespace Emby.Server.Implementations.Data
if (item.Studios.Length > 0)
{
- saveItemStatement.TryBind("@Studios", string.Join("|", item.Studios));
+ saveItemStatement.TryBind("@Studios", string.Join('|', item.Studios));
}
else
{
@@ -785,7 +785,7 @@ namespace Emby.Server.Implementations.Data
if (item.Tags.Length > 0)
{
- saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags));
+ saveItemStatement.TryBind("@Tags", string.Join('|', item.Tags));
}
else
{
@@ -807,7 +807,7 @@ namespace Emby.Server.Implementations.Data
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
{
- saveItemStatement.TryBind("@TrailerTypes", string.Join("|", trailer.TrailerTypes));
+ saveItemStatement.TryBind("@TrailerTypes", string.Join('|', trailer.TrailerTypes));
}
else
{
@@ -902,7 +902,7 @@ namespace Emby.Server.Implementations.Data
if (item.ProductionLocations.Length > 0)
{
- saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations));
+ saveItemStatement.TryBind("@ProductionLocations", string.Join('|', item.ProductionLocations));
}
else
{
@@ -911,7 +911,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0)
{
- saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
+ saveItemStatement.TryBind("@ExtraIds", string.Join('|', item.ExtraIds));
}
else
{
@@ -931,7 +931,7 @@ namespace Emby.Server.Implementations.Data
string artists = null;
if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0)
{
- artists = string.Join("|", hasArtists.Artists);
+ artists = string.Join('|', hasArtists.Artists);
}
saveItemStatement.TryBind("@Artists", artists);
@@ -940,7 +940,7 @@ namespace Emby.Server.Implementations.Data
if (item is IHasAlbumArtist hasAlbumArtists
&& hasAlbumArtists.AlbumArtists.Count > 0)
{
- albumArtists = string.Join("|", hasAlbumArtists.AlbumArtists);
+ albumArtists = string.Join('|', hasAlbumArtists.AlbumArtists);
}
saveItemStatement.TryBind("@AlbumArtists", albumArtists);
@@ -2549,7 +2549,7 @@ namespace Emby.Server.Implementations.Data
if (groups.Count > 0)
{
- return " Group by " + string.Join(",", groups);
+ return " Group by " + string.Join(',', groups);
}
return string.Empty;
@@ -2578,7 +2578,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -2630,7 +2630,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns))
+ + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -2880,7 +2880,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns))
+ + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -2923,15 +2923,15 @@ namespace Emby.Server.Implementations.Data
if (EnableGroupByPresentationUniqueKey(query))
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
}
else
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
}
commandText += GetJoinUserDataText(query)
@@ -3039,7 +3039,7 @@ namespace Emby.Server.Implementations.Data
return string.Empty;
}
- return " ORDER BY " + string.Join(",", orderBy.Select(i =>
+ return " ORDER BY " + string.Join(',', orderBy.Select(i =>
{
var columnMap = MapOrderByField(i.Item1, query);
@@ -3137,7 +3137,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }))
+ + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -3203,7 +3203,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
- var commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText();
+ var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText();
var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
@@ -3284,7 +3284,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }))
+ + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -3327,15 +3327,15 @@ namespace Emby.Server.Implementations.Data
if (EnableGroupByPresentationUniqueKey(query))
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
}
else if (query.GroupBySeriesPresentationUniqueKey)
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
}
else
{
- commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
+ commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
}
commandText += GetJoinUserDataText(query)
@@ -3596,7 +3596,7 @@ namespace Emby.Server.Implementations.Data
}
else if (excludeTypes.Length > 1)
{
- var inClause = string.Join(",", excludeTypes.Select(i => "'" + i + "'"));
+ var inClause = string.Join(',', excludeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type not in ({inClause})");
}
}
@@ -3607,7 +3607,7 @@ namespace Emby.Server.Implementations.Data
}
else if (includeTypes.Length > 1)
{
- var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'"));
+ var inClause = string.Join(',', includeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type in ({inClause})");
}
@@ -3618,7 +3618,7 @@ namespace Emby.Server.Implementations.Data
}
else if (query.ChannelIds.Count > 1)
{
- var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
+ var inClause = string.Join(',', query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add($"ChannelId in ({inClause})");
}
@@ -4351,7 +4351,7 @@ namespace Emby.Server.Implementations.Data
}
else if (query.Years.Length > 1)
{
- var val = string.Join(",", query.Years);
+ var val = string.Join(',', query.Years);
whereClauses.Add("ProductionYear in (" + val + ")");
}
@@ -4401,7 +4401,7 @@ namespace Emby.Server.Implementations.Data
}
else if (queryMediaTypes.Length > 1)
{
- var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'"));
+ var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'"));
whereClauses.Add("MediaType in (" + val + ")");
}
@@ -4498,7 +4498,7 @@ namespace Emby.Server.Implementations.Data
var paramName = "@HasAnyProviderId" + index;
// this is a search for the placeholder
- hasProviderIds.Add("ProviderIds like " + paramName + "");
+ hasProviderIds.Add("ProviderIds like " + paramName);
// this replaces the placeholder with a value, here: %key=val%
if (statement != null)
@@ -4549,7 +4549,7 @@ namespace Emby.Server.Implementations.Data
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
- var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'"));
+ var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
}
else
@@ -4564,7 +4564,7 @@ namespace Emby.Server.Implementations.Data
}
else if (queryTopParentIds.Length > 1)
{
- var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
+ var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
if (enableItemsByName && includedItemByNameTypes.Count == 1)
{
@@ -4576,7 +4576,7 @@ namespace Emby.Server.Implementations.Data
}
else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{
- var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'"));
+ var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
}
else
@@ -4597,7 +4597,7 @@ namespace Emby.Server.Implementations.Data
if (query.AncestorIds.Length > 1)
{
- var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
+ var inClause = string.Join(',', query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
}
@@ -5148,7 +5148,7 @@ AND Type = @InternalPersonType)");
}
else if (queryPersonTypes.Count > 1)
{
- var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'"));
+ var val = string.Join(',', queryPersonTypes.Select(i => "'" + i + "'"));
whereClauses.Add("PersonType in (" + val + ")");
}
@@ -5162,7 +5162,7 @@ AND Type = @InternalPersonType)");
}
else if (queryExcludePersonTypes.Count > 1)
{
- var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'"));
+ var val = string.Join(',', queryExcludePersonTypes.Select(i => "'" + i + "'"));
whereClauses.Add("PersonType not in (" + val + ")");
}
@@ -5308,19 +5308,19 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
+ ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
var commandText = "Select Value From ItemValues where " + typeClause;
if (withItemTypes.Count > 0)
{
- var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'"));
+ var typeString = string.Join(',', withItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
}
if (excludeItemTypes.Count > 0)
{
- var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'"));
+ var typeString = string.Join(',', excludeItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))";
}
@@ -5363,7 +5363,7 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
+ ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
InternalItemsQuery typeSubQuery = null;
@@ -5427,7 +5427,7 @@ AND Type = @InternalPersonType)");
columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select "
- + string.Join(",", columns)
+ + string.Join(',', columns)
+ GetFromText()
+ GetJoinUserDataText(query);
@@ -5504,7 +5504,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
var countText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
@@ -5565,7 +5565,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
commandText = "select "
- + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
@@ -6207,9 +6207,9 @@ AND Type = @InternalPersonType)");
if (item.Type == MediaStreamType.Subtitle)
{
- item.localizedUndefined = _localization.GetLocalizedString("Undefined");
- item.localizedDefault = _localization.GetLocalizedString("Default");
- item.localizedForced = _localization.GetLocalizedString("Forced");
+ item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
+ item.LocalizedDefault = _localization.GetLocalizedString("Default");
+ item.LocalizedForced = _localization.GetLocalizedString("Forced");
}
return item;
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 2c4e8e0fc..6574db607 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(
db =>
{
- db.ExecuteAll(string.Join(";", new[] {
+ db.ExecuteAll(string.Join(';', new[] {
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index d5e1f5124..54b18a8c8 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.Dto
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
if (activeRecording != null)
{
- dto.Type = "Recording";
+ dto.Type = BaseItemKind.Recording;
dto.CanDownload = false;
dto.RunTimeTicks = null;
@@ -582,16 +582,22 @@ namespace Emby.Server.Implementations.Dto
{
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
- // Only add BlurHash for the person's image.
- baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
- foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
+ if (dto.ImageBlurHashes != null)
{
- baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>();
- foreach (var (imageId, blurHashValue) in blurHash)
+ // Only add BlurHash for the person's image.
+ baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
+ foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
{
- if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase))
+ if (blurHash != null)
{
- baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
+ baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>();
+ foreach (var (imageId, blurHashValue) in blurHash)
+ {
+ if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase))
+ {
+ baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
+ }
+ }
}
}
}
@@ -898,7 +904,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- dto.Type = item.GetClientTypeName();
+ dto.Type = item.GetBaseItemKind();
if ((item.CommunityRating ?? 0) > 0)
{
dto.CommunityRating = item.CommunityRating;
@@ -1151,7 +1157,7 @@ namespace Emby.Server.Implementations.Dto
if (episodeSeries != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
- if (!dto.ImageTags.ContainsKey(ImageType.Primary))
+ if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary))
{
AttachPrimaryImageAspectRatio(dto, episodeSeries);
}
@@ -1201,7 +1207,7 @@ namespace Emby.Server.Implementations.Dto
if (series != null)
{
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
- if (!dto.ImageTags.ContainsKey(ImageType.Primary))
+ if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary))
{
AttachPrimaryImageAspectRatio(dto, series);
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 67f23f055..f03f04e02 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
- <PackageReference Include="sharpcompress" Version="0.26.0" />
+ <PackageReference Include="sharpcompress" Version="0.28.1" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index 9486874d5..a12a6b26c 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,3 +1,5 @@
+#nullable enable
+
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
@@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary>
/// The UDP server.
/// </summary>
- private UdpServer _udpServer;
+ private UdpServer? _udpServer;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false;
@@ -71,9 +73,8 @@ namespace Emby.Server.Implementations.EntryPoints
}
_cancellationTokenSource.Cancel();
- _udpServer.Dispose();
_cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
+ _udpServer?.Dispose();
_udpServer = null;
_disposed = true;
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index fed2addf8..7e0c2c1da 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -5,6 +5,7 @@ using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Net.WebSockets;
+using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.HttpServer
writer.Advance(bytesRead);
// Make the data available to the PipeReader
- FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false);
+ FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
if (flushResult.IsCompleted)
{
// The PipeReader stopped reading
@@ -181,32 +182,16 @@ namespace Emby.Server.Implementations.HttpServer
}
WebSocketMessage<object>? stub;
+ long bytesConsumed = 0;
try
{
-
- if (buffer.IsSingleSegment)
- {
- stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
- }
- else
- {
- var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
- try
- {
- buffer.CopyTo(buf);
- stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(buf);
- }
- }
+ stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
}
catch (JsonException ex)
{
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
- _logger.LogError(ex, "Error processing web socket message");
+ _logger.LogError(ex, "Error processing web socket message: {Data}", Encoding.UTF8.GetString(buffer));
return;
}
@@ -217,27 +202,34 @@ namespace Emby.Server.Implementations.HttpServer
}
// Tell the PipeReader how much of the buffer we have consumed
- reader.AdvanceTo(buffer.End);
+ reader.AdvanceTo(buffer.GetPosition(bytesConsumed));
_logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub);
- var info = new WebSocketMessageInfo
- {
- MessageType = stub.MessageType,
- Data = stub.Data?.ToString(), // Data can be null
- Connection = this
- };
-
- if (info.MessageType == SessionMessageType.KeepAlive)
+ if (stub.MessageType == SessionMessageType.KeepAlive)
{
await SendKeepAliveResponse().ConfigureAwait(false);
}
else
{
- await OnReceive(info).ConfigureAwait(false);
+ await OnReceive(
+ new WebSocketMessageInfo
+ {
+ MessageType = stub.MessageType,
+ Data = stub.Data?.ToString(), // Data can be null
+ Connection = this
+ }).ConfigureAwait(false);
}
}
+ internal WebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed)
+ {
+ var jsonReader = new Utf8JsonReader(bytes);
+ var ret = JsonSerializer.Deserialize<WebSocketMessage<object>>(ref jsonReader, _jsonOptions);
+ bytesConsumed = jsonReader.BytesConsumed;
+ return ret;
+ }
+
private Task SendKeepAliveResponse()
{
LastKeepAliveDate = DateTime.UtcNow;
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 3cb025111..c0e757543 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -582,9 +582,7 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
{
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
-
- return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
+ return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
}
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
@@ -594,16 +592,16 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ var enumerationOptions = GetEnumerationOptions(recursive);
// On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Count == 1)
{
- return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption));
+ return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], enumerationOptions));
}
- var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption);
+ var files = new DirectoryInfo(path).EnumerateFiles("*", enumerationOptions);
if (extensions != null && extensions.Count > 0)
{
@@ -625,10 +623,10 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
{
var directoryInfo = new DirectoryInfo(path);
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ var enumerationOptions = GetEnumerationOptions(recursive);
- return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
- .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
+ return ToMetadata(directoryInfo.EnumerateDirectories("*", enumerationOptions))
+ .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", enumerationOptions)));
}
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
@@ -638,8 +636,7 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
- return Directory.EnumerateDirectories(path, "*", searchOption);
+ return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
}
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
@@ -649,16 +646,16 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ var enumerationOptions = GetEnumerationOptions(recursive);
// On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
{
- return Directory.EnumerateFiles(path, "*" + extensions[0], searchOption);
+ return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
}
- var files = Directory.EnumerateFiles(path, "*", searchOption);
+ var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
if (extensions != null && extensions.Length > 0)
{
@@ -679,8 +676,18 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
{
- var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
- return Directory.EnumerateFileSystemEntries(path, "*", searchOption);
+ return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
+ }
+
+ private EnumerationOptions GetEnumerationOptions(bool recursive)
+ {
+ return new EnumerationOptions
+ {
+ RecurseSubdirectories = recursive,
+ IgnoreInaccessible = true,
+ // Don't skip any files.
+ AttributesToSkip = 0
+ };
}
private static void RunProcess(string path, string args, string workingDirectory)
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index db27862ce..d9ffe64b3 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -1240,11 +1240,20 @@ namespace Emby.Server.Implementations.Library
return info;
}
- private string GetCollectionType(string path)
+ private CollectionTypeOptions? GetCollectionType(string path)
{
- return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false)
- .Select(Path.GetFileNameWithoutExtension)
- .FirstOrDefault(i => !string.IsNullOrEmpty(i));
+ var files = _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false);
+ foreach (var file in files)
+ {
+ // TODO: @bond use a ReadOnlySpan<char> here when Enum.TryParse supports it
+ // https://github.com/dotnet/runtime/issues/20008
+ if (Enum.TryParse<CollectionTypeOptions>(Path.GetExtension(file), true, out var res))
+ {
+ return res;
+ }
+ }
+
+ return null;
}
/// <summary>
@@ -2956,7 +2965,7 @@ namespace Emby.Server.Implementations.Library
throw new InvalidOperationException();
}
- public async Task AddVirtualFolder(string name, string collectionType, LibraryOptions options, bool refreshLibrary)
+ public async Task AddVirtualFolder(string name, CollectionTypeOptions? collectionType, LibraryOptions options, bool refreshLibrary)
{
if (string.IsNullOrWhiteSpace(name))
{
@@ -2990,9 +2999,9 @@ namespace Emby.Server.Implementations.Library
{
Directory.CreateDirectory(virtualFolderPath);
- if (!string.IsNullOrEmpty(collectionType))
+ if (collectionType != null)
{
- var path = Path.Combine(virtualFolderPath, collectionType + ".collection");
+ var path = Path.Combine(virtualFolderPath, collectionType.ToString() + ".collection");
File.WriteAllBytes(path, Array.Empty<byte>());
}
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 660ec106b..c63eb7017 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -515,7 +515,7 @@ namespace Emby.Server.Implementations.Library
}
// TODO: @bond Fix
- var json = JsonSerializer.Serialize(mediaSource, _jsonOptions);
+ var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
_logger.LogInformation("Live stream opened: " + json);
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index e9e688fa6..60f82806f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -79,11 +79,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return new MusicArtist();
}
- if (_config.Configuration.EnableSimpleArtistDetection)
- {
- return null;
- }
-
// Avoid mis-identifying top folders
if (args.Parent.IsRoot)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
index 41561916f..c76d41e5c 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PlaylistResolver.cs
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
}
// It's a directory-based playlist if the directory contains a playlist file
- var filePaths = Directory.EnumerateFiles(args.Path);
+ var filePaths = Directory.EnumerateFiles(args.Path, "*", new EnumerationOptions { IgnoreInaccessible = true });
if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
{
return new Playlist
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 2c0de661d..13b5a1c55 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -2604,7 +2604,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Movies",
- CollectionType = CollectionType.Movies
+ CollectionType = CollectionTypeOptions.Movies
};
}
@@ -2615,7 +2615,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Locations = new string[] { customPath },
Name = "Recorded Shows",
- CollectionType = CollectionType.TvShows
+ CollectionType = CollectionTypeOptions.TvShows
};
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index c80ecd6b3..57424f043 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -47,11 +47,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- var jsonString = File.ReadAllText(_dataPath, Encoding.UTF8);
- _items = JsonSerializer.Deserialize<T[]>(jsonString, _jsonOptions);
+ var bytes = File.ReadAllBytes(_dataPath);
+ _items = JsonSerializer.Deserialize<T[]>(bytes, _jsonOptions);
return;
}
- catch (Exception ex)
+ catch (JsonException ex)
{
Logger.LogError(ex, "Error deserializing {Path}", _dataPath);
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 7567ea312..6d7c5ac6e 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -35,8 +35,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
- private DateTime _lastErrorResponse;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
+ private DateTime _lastErrorResponse;
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions).ConfigureAwait(false);
+ var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
@@ -122,12 +122,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions).ConfigureAwait(false);
+ var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y);
- var programIdsWithImages =
- programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
- .ToList();
+ var programIdsWithImages = programDetails
+ .Where(p => p.hasImageArtwork).Select(p => p.programID)
+ .ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
@@ -182,8 +182,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private static int GetSizeOrder(ScheduleDirect.ImageData image)
{
- if (!string.IsNullOrWhiteSpace(image.height)
- && int.TryParse(image.height, out int value))
+ if (int.TryParse(image.height, out int value))
{
return value;
}
@@ -704,7 +703,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content;
- var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
}
@@ -776,7 +775,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Mapping Stations to Channel");
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 7842be716..63a3146aa 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -2239,7 +2239,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{
- info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info));
+ info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2283,7 +2283,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.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info));
+ info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 5ef83f274..0760e8127 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -335,11 +335,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new Uri(url).AbsoluteUri.TrimEnd('/');
}
- protected EncodingOptions GetEncodingOptions()
- {
- return Config.GetConfiguration<EncodingOptions>("encoding");
- }
-
private static string GetHdHrIdFromChannelId(string channelId)
{
return channelId.Split('_')[1];
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index cdc8c6870..f09338330 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url)
{
// parse url for channel and program
- var regExp = new Regex(@"\/ch(\d+)-?(\d*)");
+ var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)");
var match = regExp.Match(url);
if (match.Success)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index cf653f87d..b16ccc561 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
try
{
- await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false);
+ await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort, openCancellationToken).ConfigureAwait(false);
localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
tcpClient.Close();
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index c82b67b41..c4f173c7a 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -155,7 +155,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (channelIdValues.Count > 0)
{
- channel.Id = string.Join("_", channelIdValues);
+ channel.Id = string.Join('_', channelIdValues);
}
return channel;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index f7507e6ba..eeb2426f4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
EnableStreamSharing = false;
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
- });
+ }, CancellationToken.None);
}
private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 977a1c2d7..b029b7042 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -112,5 +112,8 @@
"TaskRefreshLibraryDescription": "Skandeer u media versameling vir nuwe lêers en verfris metadata.",
"TaskRefreshLibrary": "Skandeer Media Versameling",
"TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.",
- "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde"
+ "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde",
+ "Undefined": "Ongedefineerd",
+ "Forced": "Geforseer",
+ "Default": "Oorspronklik"
}
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index 1fed83276..9db3b50d9 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -8,7 +8,7 @@
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
"Channels": "Канали",
"ChapterNameValue": "Глава {0}",
- "Collections": "Колекции",
+ "Collections": "Поредици",
"DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан",
"FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}",
@@ -55,26 +55,26 @@
"NotificationOptionPluginInstalled": "Приставката е инсталирана",
"NotificationOptionPluginUninstalled": "Приставката е деинсталирана",
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
- "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
+ "NotificationOptionServerRestartRequired": "Сървърът трябва да се рестартира",
"NotificationOptionTaskFailed": "Грешка в планирана задача",
- "NotificationOptionUserLockedOut": "Потребителя е заключен",
+ "NotificationOptionUserLockedOut": "Потребителят е заключен",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки",
"Playlists": "Списъци",
"Plugin": "Приставка",
- "PluginInstalledWithName": "{0} е инсталирано",
- "PluginUninstalledWithName": "{0} е деинсталирано",
- "PluginUpdatedWithName": "{0} е обновено",
+ "PluginInstalledWithName": "{0} е инсталиранa",
+ "PluginUninstalledWithName": "{0} е деинсталиранa",
+ "PluginUpdatedWithName": "{0} е обновенa",
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
- "ServerNameNeedsToBeRestarted": "{0} е нужно да се рестартира",
+ "ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира",
"Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
- "SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят",
+ "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени",
"Sync": "Синхронизиране",
"System": "Система",
"TvShows": "Телевизионни сериали",
@@ -92,12 +92,12 @@
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}",
- "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.",
- "TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи",
+ "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.",
+ "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри",
"TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
"TaskRefreshChannels": "Обновяване на Канали",
- "TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.",
- "TaskCleanTranscode": "Изчиства директорията за прекодиране",
+ "TaskCleanTranscodeDescription": "Изтрива транскодирани файлове по-стари от един ден.",
+ "TaskCleanTranscode": "Изчиства директорията за транскодиране",
"TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
"TaskUpdatePlugins": "Актуализира добавките",
"TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
@@ -113,5 +113,8 @@
"TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Приложение",
"TasksLibraryCategory": "Библиотека",
- "TasksMaintenanceCategory": "Поддръжка"
+ "TasksMaintenanceCategory": "Поддръжка",
+ "Undefined": "Неопределено",
+ "Forced": "Принудително",
+ "Default": "По подразбиране"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index b7852eccb..fd8437b6d 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -18,10 +18,10 @@
"HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",
- "HeaderFavoriteArtists": "Artistes Preferits",
- "HeaderFavoriteEpisodes": "Episodis Preferits",
- "HeaderFavoriteShows": "Programes Preferits",
- "HeaderFavoriteSongs": "Cançons Preferides",
+ "HeaderFavoriteArtists": "Artistes Predilectes",
+ "HeaderFavoriteEpisodes": "Episodis Predilectes",
+ "HeaderFavoriteShows": "Programes Predilectes",
+ "HeaderFavoriteSongs": "Cançons Predilectes",
"HeaderLiveTV": "TV en Directe",
"HeaderNextUp": "A continuació",
"HeaderRecordingGroups": "Grups d'Enregistrament",
@@ -36,7 +36,7 @@
"MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
"MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
- "MixedContent": "Contingut mesclat",
+ "MixedContent": "Contingut barrejat",
"Movies": "Pel·lícules",
"Music": "Música",
"MusicVideos": "Vídeos musicals",
@@ -76,7 +76,7 @@
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
"Sync": "Sincronitzar",
- "System": "System",
+ "System": "Sistema",
"TvShows": "Espectacles de TV",
"User": "User",
"UserCreatedWithName": "S'ha creat l'usuari {0}",
@@ -113,5 +113,10 @@
"TasksChannelsCategory": "Canals d'internet",
"TasksApplicationCategory": "Aplicació",
"TasksLibraryCategory": "Biblioteca",
- "TasksMaintenanceCategory": "Manteniment"
+ "TasksMaintenanceCategory": "Manteniment",
+ "TaskCleanActivityLogDescription": "Eliminat entrades del registre d'activitats mes antigues que l'antiguitat configurada.",
+ "TaskCleanActivityLog": "Buidar Registre d'Activitat",
+ "Undefined": "Indefinit",
+ "Forced": "Forçat",
+ "Default": "Defecto"
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 6ab22b8a4..9d82b5878 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
- "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
+ "AuthenticationSucceededWithUserName": "{0} wurde angemeldet",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Kamerafoto wurde von {0} hochgeladen",
"Channels": "Kanäle",
@@ -94,22 +94,22 @@
"VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.",
"TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter",
- "TaskRefreshChannelsDescription": "Erneuere Internet Kanal Informationen.",
- "TaskRefreshChannels": "Erneuere Kanäle",
- "TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
- "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 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.",
+ "TaskRefreshChannelsDescription": "Aktualisiere Internet Kanal Informationen.",
+ "TaskRefreshChannels": "Aktualisiere Kanäle",
+ "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, welche älter als einen Tag sind.",
+ "TaskCleanTranscode": "Lösche Transkodier-Pfad",
+ "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.",
+ "TaskUpdatePlugins": "Aktualisiere Plugins",
+ "TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
+ "TaskRefreshPeople": "Aktualisiere Schauspieler",
+ "TaskCleanLogsDescription": "Lösche Log Dateien, die älter als {0} Tage sind.",
+ "TaskCleanLogs": "Lösche Log-Verzeichnis",
+ "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
"TaskRefreshLibrary": "Scanne Medien-Bibliothek",
- "TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.",
+ "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, welche Kapitel besitzen.",
"TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder",
- "TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.",
- "TaskCleanCache": "Leere Cache Pfad",
+ "TaskCleanCacheDescription": "Löscht nicht mehr benötigte Zwischenspeicherdateien.",
+ "TaskCleanCache": "Leere Zwischenspeicher",
"TasksChannelsCategory": "Internet Kanäle",
"TasksApplicationCategory": "Anwendung",
"TasksLibraryCategory": "Bibliothek",
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
new file mode 100644
index 000000000..3ff7eddae
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -0,0 +1,26 @@
+{
+ "NotificationOptionInstallationFailed": "Instalada fiasko",
+ "NotificationOptionAudioPlaybackStopped": "Sono de ludado haltis",
+ "NotificationOptionAudioPlayback": "Ludado de sono startis",
+ "NameSeasonUnknown": "Sezono Nekonata",
+ "NameSeasonNumber": "Sezono {0}",
+ "NameInstallFailed": "{0} instalado fiaskis",
+ "Music": "Muziko",
+ "Movies": "Filmoj",
+ "ItemRemovedWithName": "{0} forigis el la biblioteko",
+ "ItemAddedWithName": "{0} aldonis al la biblioteko",
+ "HeaderLiveTV": "Viva Televido",
+ "HeaderContinueWatching": "Daŭrigi Spektado",
+ "HeaderAlbumArtists": "Artistoj de Albumo",
+ "Folders": "Dosierujoj",
+ "DeviceOnlineWithName": "{0} estas konektita",
+ "Default": "Defaŭlte",
+ "Collections": "Kolektoj",
+ "ChapterNameValue": "Ĉapitro {0}",
+ "Channels": "Kanaloj",
+ "Books": "Libroj",
+ "Artists": "Artistoj",
+ "Application": "Aplikaĵo",
+ "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
+ "Albums": "Albumoj"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 05181116d..5d7ed243f 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLogDescription": "Elimina entradas del registro de actividad que sean más antiguas al periodo establecido.",
"TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir",
- "Forced": "Forzado"
+ "Forced": "Forzado",
+ "Default": "Predeterminado"
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_419.json b/Emby.Server.Implementations/Localization/Core/es_419.json
index 03c6d5f5d..6d2a5c7ac 100644
--- a/Emby.Server.Implementations/Localization/Core/es_419.json
+++ b/Emby.Server.Implementations/Localization/Core/es_419.json
@@ -116,5 +116,6 @@
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanActivityLog": "Limpiar Registro de Actividades",
"Undefined": "Sin definir",
- "Forced": "Forzado"
+ "Forced": "Forzado",
+ "Default": "Por Defecto"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index 7eb8e36e7..e9e4f61b8 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "پخش صدا آغاز شد",
"NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد",
"NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد",
- "NotificationOptionInstallationFailed": "نصب شکست خورد",
+ "NotificationOptionInstallationFailed": "نصب ناموفق",
"NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد",
"NotificationOptionPluginError": "خرابی افزونه",
"NotificationOptionPluginInstalled": "افزونه نصب شد",
@@ -115,5 +115,8 @@
"TasksLibraryCategory": "کتابخانه",
"TasksMaintenanceCategory": "تعمیر",
"Forced": "اجباری",
- "Default": "پیشفرض"
+ "Default": "پیشفرض",
+ "TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.",
+ "TaskCleanActivityLog": "پاکسازی سیاهه فعالیت",
+ "Undefined": "تعریف نشده"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index 954759b5c..fd6148e78 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -1,121 +1,121 @@
{
- "HeaderLiveTV": "Live-TV",
- "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
+ "HeaderLiveTV": "Live TV",
+ "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon kausi",
"NameSeasonNumber": "Kausi {0}",
"NameInstallFailed": "{0} asennus epäonnistui",
"MusicVideos": "Musiikkivideot",
"Music": "Musiikki",
"Movies": "Elokuvat",
- "MixedContent": "Sekoitettu sisältö",
+ "MixedContent": "Sekalainen sisältö",
"MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty",
- "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty",
- "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}",
- "MessageApplicationUpdated": "Jellyfin palvelin on päivitetty",
- "Latest": "Uusimmat",
- "LabelRunningTimeValue": "Toiston kesto: {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty",
+ "MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}",
+ "MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty",
+ "Latest": "Viimeisimmät",
+ "LabelRunningTimeValue": "Kesto: {0}",
"LabelIpAddressValue": "IP-osoite: {0}",
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
"ItemAddedWithName": "{0} lisättiin kirjastoon",
- "Inherit": "Periytyä",
+ "Inherit": "Peri",
"HomeVideos": "Kotivideot",
"HeaderRecordingGroups": "Tallennusryhmät",
"HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Suosikkikappaleet",
"HeaderFavoriteShows": "Suosikkisarjat",
"HeaderFavoriteEpisodes": "Suosikkijaksot",
- "HeaderFavoriteArtists": "Suosikkiartistit",
+ "HeaderFavoriteArtists": "Suosikkiesittäjät",
"HeaderFavoriteAlbums": "Suosikkialbumit",
- "HeaderContinueWatching": "Jatka katsomista",
- "HeaderAlbumArtists": "Albumin artistit",
+ "HeaderContinueWatching": "Jatka katselua",
+ "HeaderAlbumArtists": "Albumin esittäjät",
"Genres": "Tyylilajit",
"Folders": "Kansiot",
"Favorites": "Suosikit",
- "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
+ "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"",
"DeviceOnlineWithName": "{0} on yhdistetty",
- "DeviceOfflineWithName": "{0} yhteys on katkaistu",
+ "DeviceOfflineWithName": "{0} on katkaissut yhteyden",
"Collections": "Kokoelmat",
- "ChapterNameValue": "Jakso: {0}",
+ "ChapterNameValue": "Kappale {0}",
"Channels": "Kanavat",
- "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
+ "CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}",
"Books": "Kirjat",
- "AuthenticationSucceededWithUserName": "{0} todennus onnistui",
- "Artists": "Artistit",
+ "AuthenticationSucceededWithUserName": "{0} on todennettu",
+ "Artists": "Esittäjät",
"Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
"Albums": "Albumit",
"User": "Käyttäjä",
"System": "Järjestelmä",
"ScheduledTaskFailedWithName": "{0} epäonnistui",
- "PluginUpdatedWithName": "{0} päivitetty",
- "PluginInstalledWithName": "{0} asennettu",
- "Photos": "Kuvat",
- "ScheduledTaskStartedWithName": "{0} aloitettu",
- "PluginUninstalledWithName": "{0} poistettu",
+ "PluginUpdatedWithName": "{0} päivitettiin",
+ "PluginInstalledWithName": "{0} asennettiin",
+ "Photos": "Valokuvat",
+ "ScheduledTaskStartedWithName": "\"{0}\" käynnistetty",
+ "PluginUninstalledWithName": "{0} poistettiin",
"Playlists": "Soittolistat",
"VersionNumber": "Versio {0}",
- "ValueSpecialEpisodeName": "Erikois - {0}",
- "ValueHasBeenAddedToLibrary": "{0} lisättiin mediakirjastoon",
- "UserStoppedPlayingItemWithValues": "{0} toistaminen valmistui {1} laitteella {2}",
- "UserStartedPlayingItemWithValues": "{0} toistaa {1} laitteella {2}",
- "UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}",
- "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
- "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
- "UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}",
- "UserLockedOutWithName": "Käyttäjä {0} lukittu",
- "UserDownloadingItemWithValues": "{0} lataa {1}",
- "UserDeletedWithName": "Käyttäjä {0} poistettu",
- "UserCreatedWithName": "Käyttäjä {0} luotu",
- "TvShows": "TV-ohjelmat",
- "Sync": "Synkronoi",
- "SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}",
- "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.",
+ "ValueSpecialEpisodeName": "Erikoisjakso - {0}",
+ "ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon",
+ "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"",
+ "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"",
+ "UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty",
+ "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu",
+ "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"",
+ "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"",
+ "UserLockedOutWithName": "Käyttäjä {0} on lukittu",
+ "UserDownloadingItemWithValues": "{0} lataa kohdetta \"{1}\"",
+ "UserDeletedWithName": "Käyttäjä {0} on poistettu",
+ "UserCreatedWithName": "Käyttäjä {0} on luotu",
+ "TvShows": "Sarjat",
+ "Sync": "Synkronointi",
+ "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui",
+ "StartupEmbyServerIsLoading": "Jellyfin-palvelin latautuu. Yritä hetken kuluttua uudelleen.",
"Songs": "Kappaleet",
- "Shows": "Ohjelmat",
- "ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen",
- "ProviderValue": "Tarjoaja: {0}",
- "Plugin": "Liitännäinen",
- "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
- "NotificationOptionVideoPlayback": "Videota toistetaan",
- "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
- "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
- "NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen",
- "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
- "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
- "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
- "NotificationOptionPluginError": "Ongelma liitännäisessä",
- "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
+ "Shows": "Sarjat",
+ "ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen",
+ "ProviderValue": "Lähde: {0}",
+ "Plugin": "Laajennus",
+ "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu",
+ "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
+ "NotificationOptionUserLockedOut": "Käyttäjä on lukittu",
+ "NotificationOptionTaskFailed": "Ajoitettu tehtävä epäonnistui",
+ "NotificationOptionServerRestartRequired": "Tarvitaan palvelimen uudelleenkäynnistys",
+ "NotificationOptionPluginUpdateInstalled": "Laajennus on päivitetty",
+ "NotificationOptionPluginUninstalled": "Laajennus on poistettu",
+ "NotificationOptionPluginInstalled": "Laajennus on asennettu",
+ "NotificationOptionPluginError": "Laajennuksen virhe",
+ "NotificationOptionNewLibraryContent": "Sisältöä on lisätty",
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
- "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
+ "NotificationOptionCameraImageUploaded": "Kameran kuva on tallennettu",
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
- "NotificationOptionAudioPlayback": "Toistetaan ääntä",
- "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu",
- "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla",
+ "NotificationOptionAudioPlayback": "Äänen toisto aloitettu",
+ "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettiin",
+ "NotificationOptionApplicationUpdateAvailable": "Sovelluspäivitys on saatavilla",
"TasksMaintenanceCategory": "Ylläpito",
- "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
+ "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä määritettyjen metatietoasetusten mukaisesti.",
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
"TaskRefreshChannels": "Päivitä kanavat",
- "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
- "TaskCleanTranscode": "Puhdista transkoodaushakemisto",
- "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
- "TaskUpdatePlugins": "Päivitä liitännäiset",
- "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
+ "TaskCleanTranscodeDescription": "Poistaa päivää vanhemmat transkoodaustiedostot.",
+ "TaskCleanTranscode": "Puhdista transkoodauskansio",
+ "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset laajennuksille, jotka on määritetty päivittymään automaattisesti.",
+ "TaskUpdatePlugins": "Päivitä laajennukset",
+ "TaskRefreshPeopleDescription": "Päivittää mediakirjaston näyttelijöiden ja ohjaajien metatiedot.",
"TaskRefreshPeople": "Päivitä henkilöt",
- "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
- "TaskCleanLogs": "Puhdista lokihakemisto",
- "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.",
- "TaskRefreshLibrary": "Skannaa mediakirjasto",
- "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.",
- "TaskRefreshChapterImages": "Pura jakson kuvat",
- "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
- "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
- "TasksChannelsCategory": "Internet kanavat",
+ "TaskCleanLogsDescription": "Poistaa {0} päivää vanhemmat lokitiedostot.",
+ "TaskCleanLogs": "Siivoa lokikansio",
+ "TaskRefreshLibraryDescription": "Tarkastaa mediakirjastosi sisällön uusien tiedostojen varalta ja päivittää metatiedot.",
+ "TaskRefreshLibrary": "Päivitä mediakirjasto",
+ "TaskRefreshChapterImagesDescription": "Luo esikatselukuvat videoille, jotka sisältävät kappalejaon.",
+ "TaskRefreshChapterImages": "Pura kappalejaon kuvat",
+ "TaskCleanCacheDescription": "Poistaa tarpeettomiksi jääneet väliaikaistiedostot.",
+ "TaskCleanCache": "Tyhjennä välimuistikansio",
+ "TasksChannelsCategory": "Internet-kanavat",
"TasksApplicationCategory": "Sovellus",
"TasksLibraryCategory": "Kirjasto",
"Forced": "Pakotettu",
"Default": "Oletus",
- "TaskCleanActivityLogDescription": "Poistaa määritettyä vanhemmat tapahtumat aktiviteettilokista.",
- "TaskCleanActivityLog": "Tyhjennä aktiviteettiloki",
+ "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.",
+ "TaskCleanActivityLog": "Tyhjennä toimintahistoria",
"Undefined": "Määrittelemätön"
}
diff --git a/Emby.Server.Implementations/Localization/Core/fil.json b/Emby.Server.Implementations/Localization/Core/fil.json
index e5ca676a4..f18a1c030 100644
--- a/Emby.Server.Implementations/Localization/Core/fil.json
+++ b/Emby.Server.Implementations/Localization/Core/fil.json
@@ -3,101 +3,101 @@
"ValueSpecialEpisodeName": "Espesyal - {0}",
"ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya",
"UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
- "UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}",
- "UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}",
+ "UserStartedPlayingItemWithValues": "Si {0} ay nagpla-play ng {1} sa {2}",
+ "UserPolicyUpdatedWithName": "Ang user policy ay nai-update para kay {0}",
"UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
- "UserOnlineFromDevice": "Si {0} ay nakakonekta galing sa {1}",
- "UserOfflineFromDevice": "Si {0} ay nadiskonekta galing sa {1}",
+ "UserOnlineFromDevice": "Si {0} ay naka-konekta galing sa {1}",
+ "UserOfflineFromDevice": "Si {0} ay na-diskonekta galing sa {1}",
"UserLockedOutWithName": "Si {0} ay nalock out",
"UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
"UserDeletedWithName": "Natanggal na is user {0}",
"UserCreatedWithName": "Nagawa na si user {0}",
"User": "User",
- "TvShows": "Pelikula",
+ "TvShows": "Mga Palabas sa Telebisyon",
"System": "Sistema",
"Sync": "Pag-sync",
- "SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}",
- "StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.",
- "Songs": "Kanta",
- "Shows": "Pelikula",
+ "SubtitleDownloadFailureFromForItem": "Hindi nai-download ang subtitles {0} para sa {1}",
+ "StartupEmbyServerIsLoading": "Naglo-load ang Jellyfin Server. Mangyaring subukan ulit sandali.",
+ "Songs": "Mga Kanta",
+ "Shows": "Mga Pelikula",
"ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
"ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
- "ScheduledTaskFailedWithName": "Hindi gumana and {0}",
- "ProviderValue": "Ang provider ay {0}",
+ "ScheduledTaskFailedWithName": "Hindi gumana ang {0}",
+ "ProviderValue": "Tagapagtustos: {0}",
"PluginUpdatedWithName": "Naiupdate na ang {0}",
"PluginUninstalledWithName": "Naiuninstall na ang {0}",
"PluginInstalledWithName": "Nainstall na ang {0}",
"Plugin": "Plugin",
- "Playlists": "Playlists",
- "Photos": "Larawan",
+ "Playlists": "Mga Playlist",
+ "Photos": "Mga Larawan",
"NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
"NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
- "NotificationOptionUserLockedOut": "Nakalock out ang user",
+ "NotificationOptionUserLockedOut": "Naka-lock out ang user",
"NotificationOptionTaskFailed": "Hindi gumana ang scheduled task",
- "NotificationOptionServerRestartRequired": "Kailangan irestart ang server",
- "NotificationOptionPluginUpdateInstalled": "Naiupdate na ang plugin",
- "NotificationOptionPluginUninstalled": "Naiuninstall na ang plugin",
+ "NotificationOptionServerRestartRequired": "Kailangan i-restart ang server",
+ "NotificationOptionPluginUpdateInstalled": "Nai-update na ang plugin",
+ "NotificationOptionPluginUninstalled": "Nai-uninstall na ang plugin",
"NotificationOptionPluginInstalled": "Nainstall na ang plugin",
"NotificationOptionPluginError": "Hindi gumagana ang plugin",
"NotificationOptionNewLibraryContent": "May bagong content na naidagdag",
"NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti",
- "NotificationOptionCameraImageUploaded": "Naiupload na ang picture",
+ "NotificationOptionCameraImageUploaded": "Naiupload na ang litrato",
"NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog",
"NotificationOptionAudioPlayback": "Nagsimula na ang patugtog",
"NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon",
"NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon",
- "NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede idownload.",
- "NameSeasonUnknown": "Hindi alam ang season",
+ "NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede i-download.",
+ "NameSeasonUnknown": "Hindi matukoy ang season",
"NameSeasonNumber": "Season {0}",
"NameInstallFailed": "Hindi nainstall ang {0}",
- "MusicVideos": "Music video",
- "Music": "Kanta",
- "Movies": "Pelikula",
+ "MusicVideos": "Mga Music video",
+ "Music": "Mga Kanta",
+ "Movies": "Mga Pelikula",
"MixedContent": "Halo-halong content",
"MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
"MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
- "MessageApplicationUpdatedTo": "Ang Jellyfin Server ay naiupdate to {0}",
+ "MessageApplicationUpdatedTo": "Ang bersyon ng Jellyfin Server ay naiupdate sa {0}",
"MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
"Latest": "Pinakabago",
"LabelRunningTimeValue": "Oras: {0}",
- "LabelIpAddressValue": "Ang IP Address ay {0}",
+ "LabelIpAddressValue": "IP address: {0}",
"ItemRemovedWithName": "Naitanggal ang {0} sa librerya",
"ItemAddedWithName": "Naidagdag ang {0} sa librerya",
"Inherit": "Manahin",
"HeaderRecordingGroups": "Pagtatalang Grupo",
"HeaderNextUp": "Susunod",
"HeaderLiveTV": "Live TV",
- "HeaderFavoriteSongs": "Paboritong Kanta",
- "HeaderFavoriteShows": "Paboritong Pelikula",
- "HeaderFavoriteEpisodes": "Paboritong Episodes",
- "HeaderFavoriteArtists": "Paboritong Artista",
- "HeaderFavoriteAlbums": "Paboritong Albums",
- "HeaderContinueWatching": "Ituloy Manood",
- "HeaderAlbumArtists": "Artista ng Album",
- "Genres": "Kategorya",
- "Folders": "Folders",
- "Favorites": "Paborito",
- "FailedLoginAttemptWithUserName": "maling login galing {0}",
- "DeviceOnlineWithName": "nakakonekta si {0}",
- "DeviceOfflineWithName": "nadiskonekta si {0}",
- "Collections": "Koleksyon",
+ "HeaderFavoriteSongs": "Mga Paboritong Kanta",
+ "HeaderFavoriteShows": "Mga Paboritong Pelikula",
+ "HeaderFavoriteEpisodes": "Mga Paboritong Episode",
+ "HeaderFavoriteArtists": "Mga Paboritong Artista",
+ "HeaderFavoriteAlbums": "Mga Paboritong Album",
+ "HeaderContinueWatching": "Magpatuloy sa Panonood",
+ "HeaderAlbumArtists": "Mga Artista ng Album",
+ "Genres": "Mga Kategorya",
+ "Folders": "Mga Folder",
+ "Favorites": "Mga Paborito",
+ "FailedLoginAttemptWithUserName": "Maling login galing kay/sa {0}",
+ "DeviceOnlineWithName": "Nakakonekta si/ang {0}",
+ "DeviceOfflineWithName": "Nadiskonekta si/ang {0}",
+ "Collections": "Mga Koleksyon",
"ChapterNameValue": "Kabanata {0}",
- "Channels": "Channel",
- "CameraImageUploadedFrom": "May bagong larawan na naupload galing {0}",
- "Books": "Libro",
- "AuthenticationSucceededWithUserName": "{0} na patunayan",
- "Artists": "Artista",
+ "Channels": "Mga Channel",
+ "CameraImageUploadedFrom": "May bagong larawan na naupload galing sa/kay {0}",
+ "Books": "Mga Libro",
+ "AuthenticationSucceededWithUserName": "Napatunayan si/ang {0}",
+ "Artists": "Mga Artista",
"Application": "Aplikasyon",
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
- "Albums": "Albums",
+ "Albums": "Mga Album",
"TaskRefreshLibrary": "Suriin and Librerya ng Medya",
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.",
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
- "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
+ "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng sistema.",
"TasksChannelsCategory": "Palabas sa internet",
"TasksLibraryCategory": "Librerya",
"TasksMaintenanceCategory": "Pagpapanatili",
- "HomeVideos": "Sariling pelikula",
+ "HomeVideos": "Sariling video/pelikula",
"TaskRefreshPeopleDescription": "Ini-update ang metadata para sa mga aktor at direktor sa iyong librerya ng medya.",
"TaskRefreshPeople": "I-refresh ang Tauhan",
"TaskDownloadMissingSubtitlesDescription": "Hinahanap sa internet ang mga nawawalang subtiles base sa metadata configuration.",
@@ -105,14 +105,17 @@
"TaskRefreshChannelsDescription": "Ni-rerefresh ang impormasyon sa internet channels.",
"TaskRefreshChannels": "I-refresh ang Channels",
"TaskCleanTranscodeDescription": "Binubura ang transcode files na mas matanda ng isang araw.",
- "TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa automatikong pag update.",
+ "TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa awtomatikong pag-update.",
"TaskUpdatePlugins": "I-update ang Plugins",
"TaskCleanLogsDescription": "Binubura and files ng talaan na mas mantanda ng {0} araw.",
"TaskCleanTranscode": "Linisin and Direktoryo ng Transcode",
"TaskCleanLogs": "Linisin and Direktoryo ng Talaan",
"TaskRefreshLibraryDescription": "Sinusuri ang iyong librerya ng medya para sa bagong files at irefresh ang metadata.",
"TaskCleanCache": "Linisin and Direktoryo ng Cache",
- "TasksApplicationCategory": "Application",
+ "TasksApplicationCategory": "Aplikasyon",
"TaskCleanActivityLog": "Linisin ang Tala ng Aktibidad",
- "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas matanda sa naka configure na edad."
+ "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.",
+ "Default": "Default",
+ "Undefined": "Hindi tiyak",
+ "Forced": "Sapilitan"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index faee2519a..12bcd793e 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -7,5 +7,55 @@
"Books": "Libros",
"AuthenticationSucceededWithUserName": "{0} autenticouse correctamente",
"Artists": "Artistas",
- "Application": "Aplicativo"
+ "Application": "Aplicativo",
+ "NotificationOptionServerRestartRequired": "Necesario un reinicio do servidor",
+ "NotificationOptionPluginUpdateInstalled": "Actualización do Plugin instalada",
+ "NotificationOptionPluginUninstalled": "Plugin desinstalado",
+ "NotificationOptionPluginInstalled": "Plugin instalado",
+ "NotificationOptionPluginError": "Fallo do Plugin",
+ "NotificationOptionNewLibraryContent": "Novo contido engadido",
+ "NotificationOptionInstallationFailed": "Fallo na instalación",
+ "NotificationOptionCameraImageUploaded": "Imaxe da cámara subida",
+ "NotificationOptionAudioPlaybackStopped": "Reproducción de audio parada",
+ "NotificationOptionAudioPlayback": "Reproducción de audio comezada",
+ "NotificationOptionApplicationUpdateInstalled": "Actualización da aplicación instalada",
+ "NotificationOptionApplicationUpdateAvailable": "Actualización da aplicación dispoñible",
+ "NewVersionIsAvailable": "Unha nova versión do Servidor Jellyfin está dispoñible para descarga.",
+ "NameSeasonUnknown": "Tempada descoñecida",
+ "NameSeasonNumber": "Tempada {0}",
+ "NameInstallFailed": "{0} instalación fallida",
+ "MusicVideos": "Vídeos Musicais",
+ "Music": "Música",
+ "Movies": "Películas",
+ "MixedContent": "Contido Mixto",
+ "MessageServerConfigurationUpdated": "A configuración do servidor foi actualizada",
+ "MessageNamedServerConfigurationUpdatedWithValue": "A sección de configuración {0} do servidor foi actualizada",
+ "MessageApplicationUpdatedTo": "O servidor Jellyfin foi actualizado a {0}",
+ "MessageApplicationUpdated": "O servidor Jellyfin foi actualizado",
+ "Latest": "Último",
+ "LabelRunningTimeValue": "Tempo de execución: {0}",
+ "LabelIpAddressValue": "Enderezo IP: {0}",
+ "ItemRemovedWithName": "{0} foi eliminado da biblioteca",
+ "ItemAddedWithName": "{0} foi engadido a biblioteca",
+ "Inherit": "Herdar",
+ "HomeVideos": "Videos caseiros",
+ "HeaderRecordingGroups": "Grupos de Grabación",
+ "HeaderNextUp": "De seguido",
+ "HeaderLiveTV": "TV en directo",
+ "HeaderFavoriteSongs": "Cancións Favoritas",
+ "HeaderFavoriteShows": "Series de TV Favoritas",
+ "HeaderFavoriteEpisodes": "Episodios Favoritos",
+ "HeaderFavoriteArtists": "Artistas Favoritos",
+ "HeaderFavoriteAlbums": "Álbunes Favoritos",
+ "HeaderContinueWatching": "Seguir mirando",
+ "HeaderAlbumArtists": "Artistas de Album",
+ "Genres": "Xéneros",
+ "Forced": "Forzado",
+ "Folders": "Cartafoles",
+ "Favorites": "Favoritos",
+ "FailedLoginAttemptWithUserName": "Intento de incio de sesión fallido {0}",
+ "DeviceOnlineWithName": "{0} conectouse",
+ "DeviceOfflineWithName": "{0} desconectouse",
+ "Default": "Por defecto",
+ "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json
index 4cc2b378b..ef3697b15 100644
--- a/Emby.Server.Implementations/Localization/Core/hi.json
+++ b/Emby.Server.Implementations/Localization/Core/hi.json
@@ -1,5 +1,5 @@
{
- "Albums": "संग्रह",
+ "Albums": "एल्बम",
"HeaderRecordingGroups": "रिकॉर्डिंग समूह",
"HeaderNextUp": "इसके बाद",
"HeaderLiveTV": "लाइव टीवी",
@@ -26,5 +26,30 @@
"AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत",
"Artists": "कलाकारों",
"Application": "एप्लिकेशन",
- "AppDeviceValues": "एप: {0}, मशीन: {1}"
+ "AppDeviceValues": "एप: {0}, उपकरण: {1}",
+ "NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया",
+ "NotificationOptionPluginInstalled": "प्लगइन इनस्टॉल हो गया",
+ "NotificationOptionPluginError": "प्लगइन फ़ैल हो गया",
+ "NotificationOptionInstallationFailed": "इंस्टालेशन फ़ैल हो गया",
+ "NotificationOptionAudioPlaybackStopped": "संगीत बंद कर दिया गया",
+ "NotificationOptionAudioPlayback": "संगीत शुरू कर दिया गया",
+ "NotificationOptionCameraImageUploaded": "कैमरा फोटो अपलोड किया गया",
+ "NotificationOptionApplicationUpdateInstalled": "एप्लीकेशन अपडेट इनस्टॉल कर दिया है",
+ "NotificationOptionApplicationUpdateAvailable": "एप्लीकेशन अपडेट उपलभ्द है",
+ "NewVersionIsAvailable": "जेलीफिन सर्वर का एक नया वर्जन डाउनलोड के लिए उपलब्ध है।",
+ "NameSeasonUnknown": "अनजान भाग",
+ "NameSeasonNumber": "भाग {0}",
+ "NameInstallFailed": "{0} इनस्टॉल करते समय फेल हो गया है",
+ "MusicVideos": "संगीत वीडियो",
+ "Music": "संगीत",
+ "Movies": "फ़िल्म",
+ "MixedContent": "मिला-जुला कंटेंट",
+ "MessageServerConfigurationUpdated": "सर्वर कॉन्फ़िगरेशन अपडेट हो गया है",
+ "MessageNamedServerConfigurationUpdatedWithValue": "सर्वर कॉन्फ़िगरेशन भाग {0} अपडेट हो गया है",
+ "MessageApplicationUpdatedTo": "जैलीफिन सर्वर {0} में अपडेट हो गया है",
+ "MessageApplicationUpdated": "जैलीफिन सर्वर अपडेट हो गया है",
+ "Latest": "सबसे नया",
+ "LabelIpAddressValue": "आई पी एड्रेस: {0}",
+ "ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है",
+ "HomeVideos": "होम वीडियोस"
}
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index 105ef7be9..ba3513870 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -1,7 +1,7 @@
{
"Albums": "Album",
"AuthenticationSucceededWithUserName": "{0} berhasil diautentikasi",
- "AppDeviceValues": "Aplikasi : {0}, Alat : {1}",
+ "AppDeviceValues": "Aplikasi : {0}, Perangkat : {1}",
"LabelRunningTimeValue": "Waktu berjalan: {0}",
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json
index 7ce9822b6..a321e35d0 100644
--- a/Emby.Server.Implementations/Localization/Core/kk.json
+++ b/Emby.Server.Implementations/Localization/Core/kk.json
@@ -1,122 +1,122 @@
{
- "Albums": "Álbomdar",
- "AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}",
+ "Albums": "Älbomdar",
+ "AppDeviceValues": "Qoldanba: {0}, Qūrylğy: {1}",
"Application": "Qoldanba",
- "Artists": "Oryndaýshylar",
- "AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy",
- "Books": "Kitaptar",
- "CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep salyndy",
+ "Artists": "Oryndauşylar",
+ "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy",
+ "Books": "Kıtaptar",
+ "CameraImageUploadedFrom": "{0} kamerasynan jaŋa suret jüktep salyndy",
"Channels": "Arnalar",
"ChapterNameValue": "{0}-sahna",
- "Collections": "Jıyntyqtar",
- "DeviceOfflineWithName": "{0} ajyratylǵan",
- "DeviceOnlineWithName": "{0} qosylǵan",
- "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz aıaqtaldy",
- "Favorites": "Tańdaýlylar",
+ "Collections": "Jiyntyqtar",
+ "DeviceOfflineWithName": "{0} ajyratylğan",
+ "DeviceOnlineWithName": "{0} qosylğan",
+ "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy",
+ "Favorites": "Taŋdaulylar",
"Folders": "Qaltalar",
"Genres": "Janrlar",
- "HeaderAlbumArtists": "Álbom oryndaýshylary",
- "HeaderContinueWatching": "Qaraýdy jalǵastyrý",
- "HeaderFavoriteAlbums": "Tańdaýly álbomdar",
- "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar",
- "HeaderFavoriteEpisodes": "Tańdaýly bólimder",
- "HeaderFavoriteShows": "Tańdaýly kórsetimder",
- "HeaderFavoriteSongs": "Tańdaýly áýender",
- "HeaderLiveTV": "Efır",
- "HeaderNextUp": "Kezekti",
+ "HeaderAlbumArtists": "Älbom oryndauşylary",
+ "HeaderContinueWatching": "Qaraudy jalğastyru",
+ "HeaderFavoriteAlbums": "Taŋdauly älbomdar",
+ "HeaderFavoriteArtists": "Taŋdauly oryndauşylar",
+ "HeaderFavoriteEpisodes": "Taŋdauly telebölımder",
+ "HeaderFavoriteShows": "Taŋdauly körsetımder",
+ "HeaderFavoriteSongs": "Taŋdauly äuender",
+ "HeaderLiveTV": "Efir",
+ "HeaderNextUp": "Kezektı",
"HeaderRecordingGroups": "Jazba toptary",
- "HomeVideos": "Úılik beıneler",
- "Inherit": "Muraǵa ıelený",
- "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústeldi",
- "ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy",
- "LabelIpAddressValue": "IP-mekenjaıy: {0}",
- "LabelRunningTimeValue": "Oınatý ýaqyty: {0}",
- "Latest": "Eń keıingi",
- "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy",
- "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server konfıgýrasýasynyń {0} bólimi jańartyldy",
- "MessageServerConfigurationUpdated": "Server konfıgýrasıasy jańartyldy",
- "MixedContent": "Aralas mazmun",
- "Movies": "Fılmder",
- "Music": "Mýzyka",
- "MusicVideos": "Mýzykalyq beıneler",
- "NameInstallFailed": "{0} ornatylýy sátsiz",
- "NameSeasonNumber": "{0}-maýsym",
- "NameSeasonUnknown": "Belgisiz maýsym",
- "NewVersionIsAvailable": "Jańa Jellyfin Server nusqasy júktep alýǵa qoljetimdi.",
- "NotificationOptionApplicationUpdateAvailable": "Qoldanba jańartýy qoljetimdi",
- "NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy",
- "NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy",
- "NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy",
- "NotificationOptionCameraImageUploaded": "Kameradan fotosýret júktep salynǵan",
- "NotificationOptionInstallationFailed": "Ornatý sátsizdigi",
- "NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen",
- "NotificationOptionPluginError": "Plagın sátsizdigi",
- "NotificationOptionPluginInstalled": "Plagın ornatyldy",
- "NotificationOptionPluginUninstalled": "Plagın ornatýy boldyrylmady",
- "NotificationOptionPluginUpdateInstalled": "Plagın jańartýy ornatyldy",
- "NotificationOptionServerRestartRequired": "Serverdi qaıta iske qosý qajet",
- "NotificationOptionTaskFailed": "Josparlaǵan tapsyrma sátsizdigi",
- "NotificationOptionUserLockedOut": "Paıdalanýshy qursaýly",
- "NotificationOptionVideoPlayback": "Beıne oınatýy bastaldy",
- "NotificationOptionVideoPlaybackStopped": "Beıne oınatýy toqtatyldy",
- "Photos": "Fotosýretter",
- "Playlists": "Oınatý tizimderi",
- "Plugin": "Plagın",
+ "HomeVideos": "Üilık beineler",
+ "Inherit": "İelenu",
+ "ItemAddedWithName": "{0} tasyğyşhanağa üstelindı",
+ "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy",
+ "LabelIpAddressValue": "IP-mekenjaiy: {0}",
+ "LabelRunningTimeValue": "Oinatu uaqyty: {0}",
+ "Latest": "Eŋ keiıngı",
+ "MessageApplicationUpdated": "Jellyfin Serverı jaŋartyldy",
+ "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jaŋartyldy",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server teŋşelımderınıŋ {0} bölımı jaŋartyldy",
+ "MessageServerConfigurationUpdated": "Server teŋşelımderı jaŋartyldy",
+ "MixedContent": "Aralas mazmūn",
+ "Movies": "Filmder",
+ "Music": "Muzyka",
+ "MusicVideos": "Muzykalyq beineler",
+ "NameInstallFailed": "{0} ornatyluy sätsız",
+ "NameSeasonNumber": "{0}-mausym",
+ "NameSeasonUnknown": "Belgısız mausym",
+ "NewVersionIsAvailable": "Jaŋa Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.",
+ "NotificationOptionApplicationUpdateAvailable": "Qoldanba jaŋartuy qoljetımdı",
+ "NotificationOptionApplicationUpdateInstalled": "Qoldanba jaŋartuy ornatyldy",
+ "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy",
+ "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy",
+ "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan",
+ "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı",
+ "NotificationOptionNewLibraryContent": "Jaŋa mazmūn üstelıngen",
+ "NotificationOptionPluginError": "Plagin sätsızdıgı",
+ "NotificationOptionPluginInstalled": "Plagin ornatyldy",
+ "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady",
+ "NotificationOptionPluginUpdateInstalled": "Plagin jaŋartuy ornatyldy",
+ "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet",
+ "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı",
+ "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly",
+ "NotificationOptionVideoPlayback": "Beine oinatuy bastaldy",
+ "NotificationOptionVideoPlaybackStopped": "Beine oinatuy toqtatyldy",
+ "Photos": "Fotosuretter",
+ "Playlists": "Oinatu tızımderı",
+ "Plugin": "Plagin",
"PluginInstalledWithName": "{0} ornatyldy",
- "PluginUninstalledWithName": "{0} joıyldy",
- "PluginUpdatedWithName": "{0} jańartyldy",
- "ProviderValue": "Jetkizýshi: {0}",
- "ScheduledTaskFailedWithName": "{0} sátsiz",
- "ScheduledTaskStartedWithName": "{0} iske qosyldy",
- "ServerNameNeedsToBeRestarted": "{0} qaıta iske qosý qajet",
- "Shows": "Kórsetimder",
- "Songs": "Áýender",
- "StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.",
+ "PluginUninstalledWithName": "{0} joiyldy",
+ "PluginUpdatedWithName": "{0} jaŋartyldy",
+ "ProviderValue": "Jetkızuşı: {0}",
+ "ScheduledTaskFailedWithName": "{0} sätsız",
+ "ScheduledTaskStartedWithName": "{0} ıske qosyldy",
+ "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet",
+ "Shows": "Körsetımder",
+ "Songs": "Äuender",
+ "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalaŋyz.",
"SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз",
- "SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz",
- "Sync": "Úndestirý",
- "System": "Júıe",
- "TvShows": "TD-kórsetimder",
- "User": "Paıdalanýshy",
- "UserCreatedWithName": "Paıdalanýshy {0} jasalǵan",
- "UserDeletedWithName": "Paıdalanýshy {0} joıylǵan",
- "UserDownloadingItemWithValues": "{0} mynany júktep alýda: {1}",
- "UserLockedOutWithName": "Paıdalanýshy {0} qursaýly",
- "UserOfflineFromDevice": "{0} - {1} tarapynan ajyratylǵan",
- "UserOnlineFromDevice": "{0} - {1} arqyly qosylǵan",
- "UserPasswordChangedWithName": "Paıdalanýshy {0} úshin paról ózgertildi",
- "UserPolicyUpdatedWithName": "Paıdalanýshy {0} úshin saıasattary jańartyldy",
- "UserStartedPlayingItemWithValues": "{0} - {1} oınatýyn {2} bastady",
- "UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty",
- "ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)",
- "ValueSpecialEpisodeName": "Arnaıy - {0}",
- "VersionNumber": "Nusqasy {0}",
- "Default": "Ádepki",
- "TaskDownloadMissingSubtitles": "Joq sýbtıtrlerdi júktep alý",
- "TaskRefreshChannels": "Arnalardy jańartý",
- "TaskCleanTranscode": "Qaıta kodtaý katalogyn tazalaý",
- "TaskUpdatePlugins": "Plagınderdi jańartý",
- "TaskRefreshPeople": "Adamdardy jańartý",
- "TaskCleanLogs": "Jurnal katalogyn tazalaý",
- "TaskRefreshLibrary": "Tasyǵyshhanany skanerleý",
- "TaskRefreshChapterImages": "Sahna keskinderin shyǵaryp alý",
- "TaskCleanCache": "Kesh katalogyn tazalaý",
- "TaskCleanActivityLog": "Áreket jurnalyn tazalaý",
+ "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız",
+ "Sync": "Ündestıru",
+ "System": "Jüie",
+ "TvShows": "TD-körsetımder",
+ "User": "Paidalanuşy",
+ "UserCreatedWithName": "Paidalanuşy {0} jasalğan",
+ "UserDeletedWithName": "Paidalanuşy {0} joiylğan",
+ "UserDownloadingItemWithValues": "{0} — {1} jüktep aluda",
+ "UserLockedOutWithName": "Paidalanuşy {0} qūrsaulanğan",
+ "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy",
+ "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy",
+ "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı",
+ "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jaŋartyldy",
+ "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda",
+ "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty",
+ "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı",
+ "ValueSpecialEpisodeName": "Arnaiy - {0}",
+ "VersionNumber": "Nūsqasy {0}",
+ "Default": "Ädepkı",
+ "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu",
+ "TaskRefreshChannels": "Arnalardy jaŋğyrtu",
+ "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau",
+ "TaskUpdatePlugins": "Plaginderdı jaŋartu",
+ "TaskRefreshPeople": "Adamdardy jaŋğyrtu",
+ "TaskCleanLogs": "Jūrnal katalogyn tazalau",
+ "TaskRefreshLibrary": "Tasyğyşhanany skanerleu",
+ "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu",
+ "TaskCleanCache": "Keş katalogyn tazalau",
+ "TaskCleanActivityLog": "Äreket jūrnalyn tazalau",
"TasksChannelsCategory": "Internet-arnalar",
"TasksApplicationCategory": "Qoldanba",
- "TasksLibraryCategory": "Tasyǵyshhana",
- "TasksMaintenanceCategory": "Qyzmet kórsetý",
- "Undefined": "Anyqtalmady",
- "Forced": "Májbúrli",
- "TaskDownloadMissingSubtitlesDescription": "Metaderekter teńshelimi negіzіnde joq sýbtıtrlerdі Internetten іzdeıdі.",
- "TaskRefreshChannelsDescription": "Internet-arnalar málimetterin jańartady.",
- "TaskCleanTranscodeDescription": "Bіr kúnnen asqan qaıta kodtaý faıldaryn joıady.",
- "TaskUpdatePluginsDescription": "Avtomatty túrde jańartýǵa teńshelgen plagınder úshin jańartýlardy júktep alady jáne ornatady.",
- "TaskRefreshPeopleDescription": "Tasyǵyshhanadaǵy aktórler men rejısórler metaderekterіn jańartady.",
- "TaskCleanLogsDescription": "{0} kúnnen asqan jurnal faıldaryn joıady.",
- "TaskRefreshLibraryDescription": "Tasyǵyshhanadaǵy jańa faıldardy skanerleıdі jáne metaderekterdі jańartady.",
- "TaskRefreshChapterImagesDescription": "Sahnalarǵa bólіngen beıneler úshіn nobaılar jasaıdy.",
- "TaskCleanCacheDescription": "Júıede qajet emes keshtelgen faıldardy joıady.",
- "TaskCleanActivityLogDescription": "Áreketter jurnalyndaǵy teńshelgen jasynan asqan jazbalaly joıady."
+ "TasksLibraryCategory": "Tasyğyşhana",
+ "TasksMaintenanceCategory": "Qyzmet körsetu",
+ "Undefined": "Anyqtalmağan",
+ "Forced": "Mäjbürlı",
+ "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.",
+ "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.",
+ "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.",
+ "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.",
+ "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
+ "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
+ "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
+ "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.",
+ "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
+ "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 3b016fe62..d5bca9f6c 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Tøm aktivitetslogg",
"Undefined": "Udefinert",
"Forced": "Tvungen",
- "Default": "Standard"
+ "Default": "Standard",
+ "TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen."
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 03d30247a..46b47cf4a 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -89,8 +89,8 @@
"UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
"UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
"UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
- "ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
- "ValueSpecialEpisodeName": "Специальный эпизод - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} добавлено в медиатеку",
+ "ValueSpecialEpisodeName": "Спецэпизод - {0}",
"VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
"TaskRefreshChannels": "Обновление каналов",
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index d785bcb90..15fb34186 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -4,11 +4,11 @@
"VersionNumber": "Верзија {0}",
"ValueSpecialEpisodeName": "Специјал - {0}",
"ValueHasBeenAddedToLibrary": "{0} је додато у вашу медијску библиотеку",
- "UserStoppedPlayingItemWithValues": "{0} заврши пуштање {1} на {2}",
+ "UserStoppedPlayingItemWithValues": "{0} завршио пуштање {1} на {2}",
"UserStartedPlayingItemWithValues": "{0} пушта {1} на {2}",
"UserPasswordChangedWithName": "Лозинка је промењена за корисника {0}",
"UserOnlineFromDevice": "{0} је на вези од {1}",
- "UserOfflineFromDevice": "{0} се одвезао са {1}",
+ "UserOfflineFromDevice": "{0} је прекинуо/а везу са {1}",
"UserLockedOutWithName": "Корисник {0} је закључан",
"UserDownloadingItemWithValues": "{0} преузима {1}",
"UserDeletedWithName": "Корисник {0} је обрисан",
@@ -41,7 +41,7 @@
"NotificationOptionPluginError": "Грешка прикључка",
"NotificationOptionNewLibraryContent": "Додат нови садржај",
"NotificationOptionInstallationFailed": "Неуспела инсталација",
- "NotificationOptionCameraImageUploaded": "Слика са камере послата",
+ "NotificationOptionCameraImageUploaded": "Слика са камере отпремљена",
"NotificationOptionAudioPlaybackStopped": "Заустављено пуштање звука",
"NotificationOptionAudioPlayback": "Покренуто пуштање звука",
"NotificationOptionApplicationUpdateInstalled": "Ажурирање инсталирано",
@@ -86,7 +86,7 @@
"Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографија је учитана са {0}",
"Books": "Књиге",
- "AuthenticationSucceededWithUserName": "{0} успешно проверено",
+ "AuthenticationSucceededWithUserName": "{0} Успешна аутентикација",
"Artists": "Извођачи",
"Application": "Апликација",
"AppDeviceValues": "Апликација: {0}, Уређај: {1}",
@@ -100,7 +100,7 @@
"TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.",
"TaskUpdatePlugins": "Ажурирајте додатке",
"TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.",
- "TaskRefreshPeople": "Освежите људе",
+ "TaskRefreshPeople": "Освежите кориснике",
"TaskCleanLogsDescription": "Брише логове старије од {0} дана.",
"TaskCleanLogs": "Очистите директоријум логова",
"TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.",
@@ -116,6 +116,6 @@
"TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.",
"TaskCleanActivityLog": "Очисти историју активности",
"Undefined": "Недефинисано",
- "Forced": "Форсирано",
+ "Forced": "Принудно",
"Default": "Подразумевано"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 552710d70..345d41e9e 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -117,5 +117,5 @@
"TaskCleanActivityLogDescription": "Radera aktivitets logg inlägg som är äldre än definerad ålder.",
"TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad",
- "Forced": "Tvinga"
+ "Forced": "Tvingad"
}
diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json
index 885663eed..c6b904045 100644
--- a/Emby.Server.Implementations/Localization/Core/tr.json
+++ b/Emby.Server.Implementations/Localization/Core/tr.json
@@ -12,7 +12,7 @@
"DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı",
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
- "Favorites": "Favorilerim",
+ "Favorites": "Favoriler",
"Folders": "Klasörler",
"Genres": "Türler",
"HeaderAlbumArtists": "Albüm Sanatçıları",
@@ -117,5 +117,6 @@
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
"TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
"Undefined": "Bilinmeyen",
- "Default": "Varsayılan"
+ "Default": "Varsayılan",
+ "Forced": "Zorla"
}
diff --git a/Emby.Server.Implementations/Localization/Core/vi.json b/Emby.Server.Implementations/Localization/Core/vi.json
index 40368d464..58652c469 100644
--- a/Emby.Server.Implementations/Localization/Core/vi.json
+++ b/Emby.Server.Implementations/Localization/Core/vi.json
@@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
- "HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
+ "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@@ -13,7 +13,7 @@
"Songs": "Các Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
- "Albums": "Albums",
+ "Albums": "Tuyển Tập",
"Artists": "Các Nghệ Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-HK.json b/Emby.Server.Implementations/Localization/Core/zh-HK.json
index 435e294ef..3dad21dcb 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-HK.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-HK.json
@@ -113,5 +113,9 @@
"TaskCleanCache": "清理緩存目錄",
"TasksChannelsCategory": "互聯網頻道",
"TasksLibraryCategory": "庫",
- "TaskRefreshPeople": "刷新人物"
+ "TaskRefreshPeople": "刷新人物",
+ "TaskCleanActivityLog": "清理活動記錄",
+ "Undefined": "未定義",
+ "Forced": "強制",
+ "Default": "預設"
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 6494c0b54..affb0e099 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -1,6 +1,6 @@
{
"Albums": "專輯",
- "AppDeviceValues": "軟體:{0},裝置:{1}",
+ "AppDeviceValues": "App:{0},裝置:{1}",
"Application": "應用程式",
"Artists": "演出者",
"AuthenticationSucceededWithUserName": "{0} 成功授權",
diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt
index 40f8614f1..488901822 100644
--- a/Emby.Server.Implementations/Localization/iso6392.txt
+++ b/Emby.Server.Implementations/Localization/iso6392.txt
@@ -77,6 +77,8 @@ chb|||Chibcha|chibcha
che||ce|Chechen|tchétchène
chg|||Chagatai|djaghataï
chi|zho|zh|Chinese|chinois
+chi|zho|zh-tw|Chinese; Traditional|chinois
+chi|zho|zh-hk|Chinese; Hong Kong|chinois
chk|||Chuukese|chuuk
chm|||Mari|mari
chn|||Chinook jargon|chinook, jargon
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index f27305cbe..c6e931448 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.MediaEncoder
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(",", video.Path));
+ _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(',', video.Path));
success = false;
break;
}
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 1ab01252d..7bc9f0a7e 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -1,8 +1,10 @@
#nullable enable
+
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
@@ -11,9 +13,11 @@ using MediaBrowser.Common;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Json.Converters;
+using MediaBrowser.Common.Net;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Updates;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -33,6 +37,21 @@ namespace Emby.Server.Implementations.Plugins
private readonly IList<LocalPlugin> _plugins;
private readonly Version _minimumVersion;
+ private IHttpClientFactory? _httpClientFactory;
+
+ private IHttpClientFactory HttpClientFactory
+ {
+ get
+ {
+ if (_httpClientFactory == null)
+ {
+ _httpClientFactory = _appHost.Resolve<IHttpClientFactory>();
+ }
+
+ return _httpClientFactory;
+ }
+ }
+
/// <summary>
/// Initializes a new instance of the <see cref="PluginManager"/> class.
/// </summary>
@@ -112,8 +131,6 @@ namespace Emby.Server.Implementations.Plugins
{
assembly = Assembly.LoadFrom(file);
- // This force loads all reference dll's that the plugin uses in the try..catch block.
- // Removing this will cause JF to bomb out if referenced dll's cause issues.
assembly.GetExportedTypes();
}
catch (FileLoadException ex)
@@ -122,6 +139,20 @@ namespace Emby.Server.Implementations.Plugins
ChangePluginState(plugin, PluginStatus.Malfunctioned);
continue;
}
+ catch (TypeLoadException ex) // Undocumented exception
+ {
+ _logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file);
+ ChangePluginState(plugin, PluginStatus.NotSupported);
+ continue;
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ _logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin.", file);
+ ChangePluginState(plugin, PluginStatus.Malfunctioned);
+ continue;
+ }
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file);
yield return assembly;
@@ -320,32 +351,74 @@ namespace Emby.Server.Implementations.Plugins
ChangePluginState(plugin, PluginStatus.Malfunctioned);
}
- /// <summary>
- /// Saves the manifest back to disk.
- /// </summary>
- /// <param name="manifest">The <see cref="PluginManifest"/> to save.</param>
- /// <param name="path">The path where to save the manifest.</param>
- /// <returns>True if successful.</returns>
+ /// <inheritdoc/>
public bool SaveManifest(PluginManifest manifest, string path)
{
- if (manifest == null)
- {
- return false;
- }
-
try
{
var data = JsonSerializer.Serialize(manifest, _jsonOptions);
- File.WriteAllText(Path.Combine(path, "meta.json"), data, Encoding.UTF8);
+ File.WriteAllText(Path.Combine(path, "meta.json"), data);
return true;
}
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception e)
-#pragma warning restore CA1031 // Do not catch general exception types
+ catch (ArgumentException e)
+ {
+ _logger.LogWarning(e, "Unable to save plugin manifest due to invalid value. {Path}", path);
+ return false;
+ }
+ }
+
+ /// <inheritdoc/>
+ public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path)
+ {
+ if (packageInfo == null)
{
- _logger.LogWarning(e, "Unable to save plugin manifest. {Path}", path);
return false;
}
+
+ var versionInfo = packageInfo.Versions.First(v => v.Version == version.ToString());
+ var imagePath = string.Empty;
+
+ if (!string.IsNullOrEmpty(packageInfo.ImageUrl))
+ {
+ var url = new Uri(packageInfo.ImageUrl);
+ imagePath = Path.Join(path, url.Segments[^1]);
+
+ await using var fileStream = File.OpenWrite(imagePath);
+
+ try
+ {
+ await using var downloadStream = await HttpClientFactory
+ .CreateClient(NamedClient.Default)
+ .GetStreamAsync(url)
+ .ConfigureAwait(false);
+
+ await downloadStream.CopyToAsync(fileStream).ConfigureAwait(false);
+ }
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
+ imagePath = string.Empty;
+ }
+ }
+
+ var manifest = new PluginManifest
+ {
+ Category = packageInfo.Category,
+ Changelog = versionInfo.Changelog ?? string.Empty,
+ Description = packageInfo.Description,
+ Id = new Guid(packageInfo.Id),
+ Name = packageInfo.Name,
+ Overview = packageInfo.Overview,
+ Owner = packageInfo.Owner,
+ TargetAbi = versionInfo.TargetAbi ?? string.Empty,
+ Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
+ Version = versionInfo.Version,
+ Status = PluginStatus.Active,
+ AutoUpdate = true,
+ ImagePath = imagePath
+ };
+
+ return SaveManifest(manifest, path);
}
/// <summary>
@@ -374,7 +447,7 @@ namespace Emby.Server.Implementations.Plugins
private LocalPlugin? GetPluginByAssembly(Assembly assembly)
{
// Find which plugin it is by the path.
- return _plugins.FirstOrDefault(p => string.Equals(p.Path, Path.GetDirectoryName(assembly.Location), StringComparison.Ordinal));
+ return _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location, StringComparer.Ordinal));
}
/// <summary>
@@ -398,7 +471,7 @@ namespace Emby.Server.Implementations.Plugins
if (plugin == null)
{
// Create a dummy record for the providers.
- // TODO: remove this code, if all provided have been released as separate plugins.
+ // TODO: remove this code once all provided have been released as separate plugins.
plugin = new LocalPlugin(
instance.AssemblyFilePath,
true,
@@ -421,15 +494,17 @@ namespace Emby.Server.Implementations.Plugins
{
plugin.Instance = instance;
var manifest = plugin.Manifest;
- var pluginStr = plugin.Instance.Version.ToString();
+ var pluginStr = instance.Version.ToString();
bool changed = false;
- if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal))
+ if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal)
+ || manifest.Id != instance.Id)
{
// If a plugin without a manifest failed to load due to an external issue (eg config),
// this updates the manifest to the actual plugin values.
manifest.Version = pluginStr;
manifest.Name = plugin.Instance.Name;
manifest.Description = plugin.Instance.Description;
+ manifest.Id = plugin.Instance.Id;
changed = true;
}
@@ -505,39 +580,43 @@ namespace Emby.Server.Implementations.Plugins
return _plugins.Remove(plugin);
}
- private LocalPlugin LoadManifest(string dir)
+ internal LocalPlugin LoadManifest(string dir)
{
Version? version;
PluginManifest? manifest = null;
var metafile = Path.Combine(dir, "meta.json");
if (File.Exists(metafile))
{
+ // Only path where this stays null is when File.ReadAllBytes throws an IOException
+ byte[] data = null!;
try
{
- var data = File.ReadAllText(metafile, Encoding.UTF8);
+ data = File.ReadAllBytes(metafile);
manifest = JsonSerializer.Deserialize<PluginManifest>(data, _jsonOptions);
}
-#pragma warning disable CA1031 // Do not catch general exception types
- catch (Exception ex)
-#pragma warning restore CA1031 // Do not catch general exception types
+ catch (IOException ex)
{
- _logger.LogError(ex, "Error deserializing {Path}.", dir);
+ _logger.LogError(ex, "Error reading file {Path}.", dir);
}
- }
-
- if (manifest != null)
- {
- if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
+ catch (JsonException ex)
{
- targetAbi = _minimumVersion;
+ _logger.LogError(ex, "Error deserializing {Json}.", Encoding.UTF8.GetString(data!));
}
- if (!Version.TryParse(manifest.Version, out version))
+ if (manifest != null)
{
- manifest.Version = _minimumVersion.ToString();
- }
+ if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
+ {
+ targetAbi = _minimumVersion;
+ }
- return new LocalPlugin(dir, _appVersion >= targetAbi, manifest);
+ if (!Version.TryParse(manifest.Version, out version))
+ {
+ manifest.Version = _minimumVersion.ToString();
+ }
+
+ return new LocalPlugin(dir, _appVersion >= targetAbi, manifest);
+ }
}
// No metafile, so lets see if the folder is versioned.
@@ -559,7 +638,7 @@ namespace Emby.Server.Implementations.Plugins
// Auto-create a plugin manifest, so we can disable it, if it fails to load.
manifest = new PluginManifest
{
- Status = PluginStatus.Restart,
+ Status = PluginStatus.Active,
Name = metafile,
AutoUpdate = false,
Id = metafile.GetMD5(),
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 29440b64a..b302303f8 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -143,21 +143,21 @@ namespace Emby.Server.Implementations.ScheduledTasks
{
if (File.Exists(path))
{
- try
+ var bytes = File.ReadAllBytes(path);
+ if (bytes.Length > 0)
{
- var jsonString = File.ReadAllText(path, Encoding.UTF8);
- if (!string.IsNullOrWhiteSpace(jsonString))
+ try
{
- _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(jsonString, _jsonOptions);
+ _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions);
}
- else
+ catch (JsonException ex)
{
- _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
+ _logger.LogError(ex, "Error deserializing {File}", path);
}
}
- catch (Exception ex)
+ else
{
- _logger.LogError(ex, "Error deserializing {File}", path);
+ _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
}
}
@@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
lock (_lastExecutionResultSyncLock)
{
- using FileStream createStream = File.OpenWrite(path);
+ using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
JsonSerializer.SerializeAsync(createStream, value, _jsonOptions);
}
}
@@ -541,8 +541,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
TaskTriggerInfo[] list = null;
if (File.Exists(path))
{
- var jsonString = File.ReadAllText(path, Encoding.UTF8);
- list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(jsonString, _jsonOptions);
+ var bytes = File.ReadAllBytes(path);
+ list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions);
}
// Return defaults if file doesn't exist.
@@ -577,9 +577,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
var path = GetConfigurationFilePath();
Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- var json = JsonSerializer.Serialize(triggers, _jsonOptions);
- File.WriteAllText(path, json, Encoding.UTF8);
+ using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ JsonSerializer.SerializeAsync(createStream, triggers, _jsonOptions);
}
/// <summary>
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 171e44258..649305fd5 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
Directory.CreateDirectory(parentPath);
- string text = string.Join("|", previouslyFailedImages);
+ string text = string.Join('|', previouslyFailedImages);
File.WriteAllText(failHistoryPath, text);
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index 184d155d4..fedb5deb0 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -80,10 +80,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
// Delete log files more than n days old
var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays);
- // Only delete the .txt log files, the *.log files created by serilog get managed by itself
- var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, new[] { ".txt" }, true, true)
- .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
- .ToList();
+ // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_'
+ var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true)
+ .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal)
+ && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
+ .ToList();
var index = 0;
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index 8b67d37d7..3b40320ab 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var dueTime = triggerDate - now;
- logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime);
+ logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 4e026a0e6..10e28c33a 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1456,7 +1456,12 @@ namespace Emby.Server.Implementations.Session
throw new SecurityException("Unknown quick connect token");
}
- request.UserId = result.Items[0].UserId;
+ var info = result.Items[0];
+ request.UserId = info.UserId;
+
+ // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token.
+ _authRepo.Delete(info);
+
return AuthenticateNewSessionInternal(request, false);
}
diff --git a/Emby.Server.Implementations/Session/WebSocketController.cs b/Emby.Server.Implementations/Session/WebSocketController.cs
index f9c6a13c6..a653b58c2 100644
--- a/Emby.Server.Implementations/Session/WebSocketController.cs
+++ b/Emby.Server.Implementations/Session/WebSocketController.cs
@@ -8,7 +8,6 @@ using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Net;
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index f0734340b..839b62448 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -143,10 +143,31 @@ namespace Emby.Server.Implementations.TV
var allNextUp = seriesKeys
.Select(i => GetNextUp(i, currentUser, dtoOptions));
+ // If viewing all next up for all series, remove first episodes
+ // But if that returns empty, keep those first episodes (avoid completely empty view)
+ var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
+ var anyFound = false;
+
return allNextUp
.Where(i =>
{
- return i.Item1 != DateTime.MinValue;
+ if (request.DisableFirstEpisode)
+ {
+ return i.Item1 != DateTime.MinValue;
+ }
+
+ if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
+ {
+ anyFound = true;
+ return true;
+ }
+
+ if (!anyFound && i.Item1 == DateTime.MinValue)
+ {
+ return true;
+ }
+
+ return false;
})
.Select(i => i.Item2())
.Where(i => i != null);
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index abcb4313f..7af52ea65 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -192,17 +192,12 @@ namespace Emby.Server.Implementations.Updates
var version = package.Versions[i];
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
- // Update the manifests, if anything changes.
if (plugin != null)
{
- if (!string.Equals(plugin.Manifest.TargetAbi, version.TargetAbi, StringComparison.Ordinal))
- {
- plugin.Manifest.TargetAbi = version.TargetAbi ?? string.Empty;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
+ await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
}
- // Remove versions with a target abi that is greater then the current application version.
+ // Remove versions with a target ABI greater then the current application version.
if (Version.TryParse(version.TargetAbi, out var targetAbi) && _applicationHost.ApplicationVersion < targetAbi)
{
package.Versions.RemoveAt(i);
@@ -294,7 +289,8 @@ namespace Emby.Server.Implementations.Updates
Name = package.Name,
Version = v.VersionNumber,
SourceUrl = v.SourceUrl,
- Checksum = v.Checksum
+ Checksum = v.Checksum,
+ PackageInfo = package
};
}
}
@@ -571,24 +567,16 @@ namespace Emby.Server.Implementations.Updates
stream.Position = 0;
_zipClient.ExtractAllFromZip(stream, targetDir, true);
+ await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
_pluginManager.ImportPluginFrom(targetDir);
}
private async Task<bool> InstallPackageInternal(InstallationInfo package, CancellationToken cancellationToken)
{
- // Set last update time if we were installed before
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
- if (plugin != null)
- {
- plugin.Manifest.Timestamp = DateTime.UtcNow;
- _pluginManager.SaveManifest(plugin.Manifest, plugin.Path);
- }
- // Do the install
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
-
- // Do plugin-specific processing
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
return plugin != null;