aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs2
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs7
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs27
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs10
-rw-r--r--Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs7
-rw-r--r--Emby.Server.Implementations/Library/IgnorePatterns.cs2
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs185
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs13
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs10
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs22
-rw-r--r--Emby.Server.Implementations/Localization/Core/be.json74
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/es_DO.json8
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ht.json61
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/pr.json72
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/sr.json13
-rw-r--r--Emby.Server.Implementations/Localization/iso6392.txt2
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs66
-rw-r--r--Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs3
-rw-r--r--Emby.Server.Implementations/Sorting/IsPlayedComparer.cs3
-rw-r--r--Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs3
28 files changed, 447 insertions, 167 deletions
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index e74755ec32..c69bcfef78 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -108,7 +108,7 @@ namespace Emby.Server.Implementations.AppBase
private void CheckOrCreateMarker(string path, string markerName, bool recursive = false)
{
var otherMarkers = GetMarkers(path, recursive).FirstOrDefault(e => Path.GetFileName(e) != markerName);
- if (otherMarkers != null)
+ if (otherMarkers is not null)
{
throw new InvalidOperationException($"Exepected to find only {markerName} but found marker for {otherMarkers}.");
}
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index 31ae82d6a3..676bb7f816 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -50,6 +50,8 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
_logger.LogDebug("Cleaning {Number} items with dead parents", numItems);
+ IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2));
+
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -95,9 +97,10 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
numComplete++;
double percent = numComplete;
percent /= numItems;
- progress.Report(percent * 100);
+ subProgress.Report(percent * 100);
}
+ subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50));
var context = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
await using (context.ConfigureAwait(false))
{
@@ -105,7 +108,9 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
await using (transaction.ConfigureAwait(false))
{
await context.ItemValues.Where(e => e.BaseItemsMap!.Count == 0).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+ subProgress.Report(50);
await transaction.CommitAsync(cancellationToken).ConfigureAwait(false);
+ subProgress.Report(100);
}
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 0db1606ea5..c5dc3b054c 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -1051,30 +1051,15 @@ namespace Emby.Server.Implementations.Dto
// Include artists that are not in the database yet, e.g., just added via metadata editor
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
- dto.ArtistItems = hasArtist.Artists
- // .Except(foundArtists, new DistinctNameComparer())
+ dto.ArtistItems = _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))])
+ .Where(e => e.Value.Length > 0)
.Select(i =>
{
- // This should not be necessary but we're seeing some cases of it
- if (string.IsNullOrEmpty(i))
- {
- return null;
- }
-
- var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
- {
- EnableImages = false
- });
- if (artist is not null)
+ return new NameGuidPair
{
- return new NameGuidPair
- {
- Name = artist.Name,
- Id = artist.Id
- };
- }
-
- return null;
+ Name = i.Key,
+ Id = i.Value.First().Id
+ };
}).Where(i => i is not null).ToArray();
}
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index f9538fbad6..ca0744a17d 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -37,6 +37,11 @@ namespace Emby.Server.Implementations.Library
return false;
}
+ if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
+ {
+ return true;
+ }
+
// Don't ignore top level folders
if (fileInfo.IsDirectory
&& (parent is AggregateFolder || (parent?.IsTopParent ?? false)))
@@ -44,11 +49,6 @@ namespace Emby.Server.Implementations.Library
return false;
}
- if (IgnorePatterns.ShouldIgnore(fileInfo.FullName))
- {
- return true;
- }
-
if (parent is null)
{
return false;
diff --git a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
index 401ca73b80..bafe3ad436 100644
--- a/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/DotIgnoreIgnoreRule.cs
@@ -50,6 +50,13 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
return false;
}
+ // Fast path in case the ignore files isn't a symlink and is empty
+ if ((dirIgnoreFile.Attributes & FileAttributes.ReparsePoint) == 0
+ && dirIgnoreFile.Length == 0)
+ {
+ return true;
+ }
+
// ignore the directory only if the .ignore file is empty
// evaluate individual files otherwise
return string.IsNullOrWhiteSpace(GetFileContent(dirIgnoreFile));
diff --git a/Emby.Server.Implementations/Library/IgnorePatterns.cs b/Emby.Server.Implementations/Library/IgnorePatterns.cs
index 25ddade829..fe3a1ce611 100644
--- a/Emby.Server.Implementations/Library/IgnorePatterns.cs
+++ b/Emby.Server.Implementations/Library/IgnorePatterns.cs
@@ -48,6 +48,8 @@ namespace Emby.Server.Implementations.Library
"**/.wd_tv",
"**/lost+found/**",
"**/lost+found",
+ "**/subs/**",
+ "**/subs",
// Trickplay files
"**/*.trickplay",
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 58a971f62a..ef497726e2 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -327,6 +327,45 @@ namespace Emby.Server.Implementations.Library
DeleteItem(item, options, parent, notifyParentItem);
}
+ public void DeleteItemsUnsafeFast(IEnumerable<BaseItem> items)
+ {
+ var pathMaps = items.Select(e => (Item: e, InternalPath: GetInternalMetadataPaths(e), DeletePaths: e.GetDeletePaths())).ToArray();
+
+ foreach (var (item, internalPaths, pathsToDelete) in pathMaps)
+ {
+ foreach (var metadataPath in internalPaths)
+ {
+ if (!Directory.Exists(metadataPath))
+ {
+ continue;
+ }
+
+ _logger.LogDebug(
+ "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ metadataPath,
+ item.Id);
+
+ try
+ {
+ Directory.Delete(metadataPath, true);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath);
+ }
+ }
+
+ foreach (var fileSystemInfo in pathsToDelete)
+ {
+ DeleteItemPath(item, false, fileSystemInfo);
+ }
+ }
+
+ _itemRepository.DeleteItem([.. pathMaps.Select(f => f.Item.Id)]);
+ }
+
public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem)
{
ArgumentNullException.ThrowIfNull(item);
@@ -403,59 +442,7 @@ namespace Emby.Server.Implementations.Library
foreach (var fileSystemInfo in item.GetDeletePaths())
{
- if (Directory.Exists(fileSystemInfo.FullName) || File.Exists(fileSystemInfo.FullName))
- {
- try
- {
- _logger.LogInformation(
- "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
- item.GetType().Name,
- item.Name ?? "Unknown name",
- fileSystemInfo.FullName,
- item.Id);
-
- if (fileSystemInfo.IsDirectory)
- {
- Directory.Delete(fileSystemInfo.FullName, true);
- }
- else
- {
- File.Delete(fileSystemInfo.FullName);
- }
- }
- catch (DirectoryNotFoundException)
- {
- _logger.LogInformation(
- "Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
- item.GetType().Name,
- item.Name ?? "Unknown name",
- fileSystemInfo.FullName,
- item.Id);
- }
- catch (FileNotFoundException)
- {
- _logger.LogInformation(
- "File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
- item.GetType().Name,
- item.Name ?? "Unknown name",
- fileSystemInfo.FullName,
- item.Id);
- }
- catch (IOException)
- {
- if (isRequiredForDelete)
- {
- throw;
- }
- }
- catch (UnauthorizedAccessException)
- {
- if (isRequiredForDelete)
- {
- throw;
- }
- }
- }
+ DeleteItemPath(item, isRequiredForDelete, fileSystemInfo);
isRequiredForDelete = false;
}
@@ -463,17 +450,73 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null);
- _itemRepository.DeleteItem(item.Id);
+ _itemRepository.DeleteItem([item.Id, .. children.Select(f => f.Id)]);
_cache.TryRemove(item.Id, out _);
foreach (var child in children)
{
- _itemRepository.DeleteItem(child.Id);
_cache.TryRemove(child.Id, out _);
}
ReportItemRemoved(item, parent);
}
+ private void DeleteItemPath(BaseItem item, bool isRequiredForDelete, FileSystemMetadata fileSystemInfo)
+ {
+ if (Directory.Exists(fileSystemInfo.FullName) || File.Exists(fileSystemInfo.FullName))
+ {
+ try
+ {
+ _logger.LogInformation(
+ "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+
+ if (fileSystemInfo.IsDirectory)
+ {
+ Directory.Delete(fileSystemInfo.FullName, true);
+ }
+ else
+ {
+ File.Delete(fileSystemInfo.FullName);
+ }
+ }
+ catch (DirectoryNotFoundException)
+ {
+ _logger.LogInformation(
+ "Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+ }
+ catch (FileNotFoundException)
+ {
+ _logger.LogInformation(
+ "File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+ }
+ catch (IOException)
+ {
+ if (isRequiredForDelete)
+ {
+ throw;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ if (isRequiredForDelete)
+ {
+ throw;
+ }
+ }
+ }
+ }
+
private bool IsInternalItem(BaseItem item)
{
if (!item.IsFileProtocol)
@@ -485,7 +528,7 @@ namespace Emby.Server.Implementations.Library
{
Genre => _configurationManager.ApplicationPaths.GenrePath,
MusicArtist => _configurationManager.ApplicationPaths.ArtistsPath,
- MusicGenre => _configurationManager.ApplicationPaths.GenrePath,
+ MusicGenre => _configurationManager.ApplicationPaths.MusicGenrePath,
Person => _configurationManager.ApplicationPaths.PeoplePath,
Studio => _configurationManager.ApplicationPaths.StudioPath,
Year => _configurationManager.ApplicationPaths.YearPath,
@@ -826,6 +869,7 @@ namespace Emby.Server.Implementations.Library
if (!folder.ParentId.Equals(rootFolder.Id))
{
+ rootFolder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
folder.ParentId = rootFolder.Id;
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, CancellationToken.None).GetAwaiter().GetResult();
}
@@ -989,6 +1033,11 @@ namespace Emby.Server.Implementations.Library
return GetArtist(name, new DtoOptions(true));
}
+ public IReadOnlyDictionary<string, MusicArtist[]> GetArtists(IReadOnlyList<string> names)
+ {
+ return _itemRepository.FindArtists(names);
+ }
+
public MusicArtist GetArtist(string name, DtoOptions options)
{
return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name, options);
@@ -1090,6 +1139,7 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
{
+ RootFolder.Children = null;
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
// Start by just validating the children of the root, but go no further
@@ -1100,9 +1150,12 @@ namespace Emby.Server.Implementations.Library
allowRemoveRoot: removeRoot,
cancellationToken: cancellationToken).ConfigureAwait(false);
- await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ var rootFolder = GetUserRootFolder();
+ rootFolder.Children = null;
+
+ await rootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
- await GetUserRootFolder().ValidateChildren(
+ await rootFolder.ValidateChildren(
new Progress<double>(),
new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false,
@@ -1110,18 +1163,24 @@ namespace Emby.Server.Implementations.Library
cancellationToken: cancellationToken).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes
- foreach (var child in GetUserRootFolder().Children.OfType<Folder>())
+ var toDelete = new List<Guid>();
+ foreach (var child in rootFolder.Children!.OfType<Folder>())
{
// If the user has somehow deleted the collection directory, remove the metadata from the database.
if (child is CollectionFolder collectionFolder && !Directory.Exists(collectionFolder.Path))
{
- _itemRepository.DeleteItem(collectionFolder.Id);
+ toDelete.Add(collectionFolder.Id);
}
else
{
await child.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
}
+
+ if (toDelete.Count > 0)
+ {
+ _itemRepository.DeleteItem(toDelete.ToArray());
+ }
}
private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken)
@@ -2027,6 +2086,12 @@ namespace Emby.Server.Implementations.Library
}
}
+ if (!File.Exists(image.Path))
+ {
+ _logger.LogWarning("Image not found at {ImagePath}", image.Path);
+ continue;
+ }
+
ImageDimensions size;
try
{
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 1e3b8ea760..750346169f 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -657,7 +657,7 @@ namespace Emby.Server.Implementations.Library
}
catch (Exception ex)
{
- _logger.LogDebug(ex, "_jsonSerializer.DeserializeFromFile threw an exception.");
+ _logger.LogDebug(ex, "Error parsing cached media info.");
}
finally
{
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 28cf695007..e0c8ae371b 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -45,11 +45,14 @@ namespace Emby.Server.Implementations.Library
public IReadOnlyList<BaseItem> GetInstantMixFromFolder(Folder item, User? user, DtoOptions dtoOptions)
{
var genres = item
- .GetRecursiveChildren(user, new InternalItemsQuery(user)
- {
- IncludeItemTypes = [BaseItemKind.Audio],
- DtoOptions = dtoOptions
- })
+ .GetRecursiveChildren(
+ user,
+ new InternalItemsQuery(user)
+ {
+ IncludeItemTypes = [BaseItemKind.Audio],
+ DtoOptions = dtoOptions
+ },
+ out _)
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index b2ceee97d8..333c8c34bf 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -405,6 +405,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (child.IsDirectory)
{
+ if (NamingOptions.AllExtrasTypesFolderNames.ContainsKey(filename))
+ {
+ continue;
+ }
+
if (IsDvdDirectory(child.FullName, filename, directoryService))
{
var movie = new T
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index be1d96bf0b..72c8d7a9d2 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -80,6 +80,7 @@ namespace Emby.Server.Implementations.Library
var userId = user.InternalId;
var cacheKey = GetCacheKey(userId, item.Id);
_cache.AddOrUpdate(cacheKey, userData);
+ item.UserData = dbContext.UserData.Where(e => e.ItemId == item.Id).AsNoTracking().ToArray(); // rehydrate the cached userdata
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{
@@ -159,7 +160,7 @@ namespace Emby.Server.Implementations.Library
};
}
- private UserItemData Map(UserData dto)
+ private static UserItemData Map(UserData dto)
{
return new UserItemData()
{
@@ -237,7 +238,10 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc />
public UserItemData? GetUserData(User user, BaseItem item)
{
- return GetUserData(user, item.Id, item.GetUserDataKeys());
+ return item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault() ?? new UserItemData()
+ {
+ Key = item.GetUserDataKeys()[0],
+ };
}
/// <inheritdoc />
@@ -304,7 +308,7 @@ namespace Emby.Server.Implementations.Library
// ignore progress during the beginning
positionTicks = 0;
}
- else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= runtimeTicks)
+ else if (pctIn > _config.Configuration.MaxResumePct || positionTicks >= (runtimeTicks - TimeSpan.TicksPerSecond))
{
// mark as completed close to the end
positionTicks = 0;
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index b7fd24fa5c..f9a6f0d19e 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -1,5 +1,5 @@
using System;
-using System.Globalization;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
@@ -55,6 +55,8 @@ public class PeopleValidator
var numPeople = people.Count;
+ IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2));
+
_logger.LogDebug("Will refresh {Amount} people", numPeople);
foreach (var person in people)
@@ -92,7 +94,7 @@ public class PeopleValidator
double percent = numComplete;
percent /= numPeople;
- progress.Report(100 * percent);
+ subProgress.Report(100 * percent);
}
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
@@ -102,17 +104,13 @@ public class PeopleValidator
IsLocked = false
});
- foreach (var item in deadEntities)
- {
- _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
+ subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50));
- _libraryManager.DeleteItem(
- item,
- new DeleteOptions
- {
- DeleteFileLocation = false
- },
- false);
+ var i = 0;
+ foreach (var item in deadEntities.Chunk(500))
+ {
+ _libraryManager.DeleteItemsUnsafeFast(item);
+ subProgress.Report(100f / deadEntities.Count * (i++ * 100));
}
progress.Report(100);
diff --git a/Emby.Server.Implementations/Localization/Core/be.json b/Emby.Server.Implementations/Localization/Core/be.json
index dec491d08b..29847048cb 100644
--- a/Emby.Server.Implementations/Localization/Core/be.json
+++ b/Emby.Server.Implementations/Localization/Core/be.json
@@ -1,16 +1,16 @@
{
"Sync": "Сінхранізаваць",
- "Playlists": "Спісы прайгравання",
- "Latest": "Апошні",
+ "Playlists": "Плэй-лісты",
+ "Latest": "Апошняе",
"LabelIpAddressValue": "IP-адрас: {0}",
- "ItemAddedWithName": "{0} быў дададзены ў бібліятэку",
+ "ItemAddedWithName": "{0} даданы ў бібліятэку",
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
- "NotificationOptionApplicationUpdateInstalled": "Абнаўленне прыкладання ўсталявана",
+ "NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана",
"PluginInstalledWithName": "{0} быў усталяваны",
"UserCreatedWithName": "Карыстальнік {0} быў створаны",
"Albums": "Альбомы",
- "Application": "Прыкладанне",
- "AuthenticationSucceededWithUserName": "{0} паспяхова аўтэнтыфікаваны",
+ "Application": "Праграма",
+ "AuthenticationSucceededWithUserName": "{0} паспяхова аўтарызаваны",
"Channels": "Каналы",
"ChapterNameValue": "Раздзел {0}",
"Collections": "Калекцыі",
@@ -29,18 +29,18 @@
"HeaderAlbumArtists": "Выканаўцы альбома",
"LabelRunningTimeValue": "Працягласць: {0}",
"HomeVideos": "Хатнія відэа",
- "ItemRemovedWithName": "{0} быў выдалены з бібліятэкі",
- "MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да {0}",
+ "ItemRemovedWithName": "{0} выдалены з бібліятэкі",
+ "MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да версіі {0}",
"Movies": "Фільмы",
"Music": "Музыка",
"MusicVideos": "Музычныя кліпы",
- "NameInstallFailed": "Устаноўка {0} не атрымалася",
+ "NameInstallFailed": "Усталяванне {0} не атрымалася",
"NameSeasonNumber": "Сезон {0}",
- "NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне прыкладання",
+ "NotificationOptionApplicationUpdateAvailable": "Даступна абнаўленне праграмы",
"NotificationOptionPluginInstalled": "Плагін усталяваны",
- "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна усталявана",
+ "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна ўсталявана",
"NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера",
- "Photos": "Фатаграфіі",
+ "Photos": "Фотаздымкі",
"Plugin": "Плагін",
"PluginUninstalledWithName": "{0} быў выдалены",
"PluginUpdatedWithName": "{0} быў абноўлены",
@@ -54,16 +54,16 @@
"Artists": "Выканаўцы",
"UserOfflineFromDevice": "{0} адлучыўся ад {1}",
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
- "TaskCleanActivityLogDescription": "Выдаляе старэйшыя за зададзены ўзрост запісы ў журнале актыўнасці.",
+ "TaskCleanActivityLogDescription": "Выдаляе запісы старэйшыя за зададзены ўзрост ў журнале актыўнасці.",
"TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.",
"TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.",
- "TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія настроены на аўтаматычнае абнаўленне.",
+ "TaskUpdatePluginsDescription": "Спампоўвае і ўсталёўвае абнаўленні для плагінаў, якія сканфігураваныя на аўтаматычнае абнаўленне.",
"TaskRefreshChannelsDescription": "Абнаўляе інфармацыю аб інтэрнэт-канале.",
- "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субтытры на аснове канфігурацыі метададзеных.",
- "TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых змяненняў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць прадукцыйнасць.",
+ "TaskDownloadMissingSubtitlesDescription": "Шукае ў інтэрнэце адсутныя субцітры на аснове канфігурацыі метададзеных.",
+ "TaskOptimizeDatabaseDescription": "Ушчыльняе базу дадзеных і скарачае вольную прастору. Выкананне гэтай задачы пасля сканавання бібліятэкі або ўнясення іншых зменаў, якія прадугледжваюць мадыфікацыю базы дадзеных, можа палепшыць выдайнасць.",
"TaskKeyframeExtractor": "Экстрактар ключавых кадраў",
- "TasksApplicationCategory": "Прыкладанне",
- "AppDeviceValues": "Прыкладанне: {0}, Прылада: {1}",
+ "TasksApplicationCategory": "Праграма",
+ "AppDeviceValues": "Праграма: {0}, Прылада: {1}",
"Books": "Кнігі",
"CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}",
"DeviceOfflineWithName": "{0} адлучыўся",
@@ -74,7 +74,7 @@
"HeaderFavoriteArtists": "Абраныя выканаўцы",
"HearingImpaired": "Са слабым слыхам",
"Inherit": "Атрымаць у спадчыну",
- "MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера {0} абноўлена",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера (секцыя {0}) абноўлена",
"MessageServerConfigurationUpdated": "Канфігурацыя сервера абноўлена",
"MixedContent": "Змешаны змест",
"NameSeasonUnknown": "Невядомы сезон",
@@ -92,48 +92,48 @@
"NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена",
"ScheduledTaskFailedWithName": "{0} не атрымалася",
"ScheduledTaskStartedWithName": "{0} пачалося",
- "ServerNameNeedsToBeRestarted": "{0} трэба перазапусціць",
+ "ServerNameNeedsToBeRestarted": "{0} патрабуе перазапуску",
"Shows": "Шоу",
"StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.",
"SubtitleDownloadFailureFromForItem": "Не атрымалася спампаваць субтытры з {0} для {1}",
- "TvShows": "ТБ-шоу",
+ "TvShows": "Тэлепраграма",
"Undefined": "Нявызначана",
"UserLockedOutWithName": "Карыстальнік {0} быў заблакіраваны",
"UserOnlineFromDevice": "{0} падключаны з {1}",
"UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}",
- "UserStartedPlayingItemWithValues": "{0} грае {1} на {2}",
+ "UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}",
"UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}",
"ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку",
"ValueSpecialEpisodeName": "Спецэпізод - {0}",
"VersionNumber": "Версія {0}",
"TasksMaintenanceCategory": "Абслугоўванне",
- "TasksLibraryCategory": "Медыятэка",
+ "TasksLibraryCategory": "Бібліятэка",
"TasksChannelsCategory": "Інтэрнэт-каналы",
"TaskCleanActivityLog": "Ачысціць журнал актыўнасці",
"TaskCleanCache": "Ачысціць кэш",
"TaskCleanCacheDescription": "Выдаляе файлы кэша, якія больш не патрэбныя сістэме.",
- "TaskRefreshChapterImages": "Выняць выявы раздзелаў",
- "TaskRefreshLibrary": "Сканіраваць медыятэку",
- "TaskRefreshLibraryDescription": "Сканіруе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
- "TaskCleanLogs": "Ачысціць часопіс",
- "TaskRefreshPeople": "Абнавіць людзей",
+ "TaskRefreshChapterImages": "Вынуць выявы раздзелаў",
+ "TaskRefreshLibrary": "Сканаваць бібліятэку",
+ "TaskRefreshLibraryDescription": "Скануе вашу медыятэку на наяўнасць новых файлаў і абнаўляе метададзеныя.",
+ "TaskCleanLogs": "Ачысціць журнал",
+ "TaskRefreshPeople": "Абнавіць выканаўцаў",
"TaskRefreshPeopleDescription": "Абнаўленне метаданых для акцёраў і рэжысёраў у вашай медыятэцы.",
"TaskUpdatePlugins": "Абнавіць плагіны",
"TaskCleanTranscode": "Ачысціць каталог перакадзіравання",
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
"TaskRefreshChannels": "Абнавіць каналы",
- "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
- "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.",
- "TaskRefreshTrickplayImages": "Стварыце выявы Trickplay",
- "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках.",
- "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання",
- "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць.",
- "TaskAudioNormalizationDescription": "Сканіруе файлы на прадмет нармалізацыі гуку.",
+ "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
+ "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.",
+ "TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
+ "TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
+ "TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
+ "TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
"TaskAudioNormalization": "Нармалізацыя гуку",
"TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Перамяшчае існуючыя файлы trickplay у адпаведнасці з наладамі бібліятэкі.",
- "TaskDownloadMissingLyrics": "Спампаваць зніклыя тэксты песень",
- "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песень",
+ "TaskDownloadMissingLyrics": "Спампаваць адсутныя тэксты песняў",
+ "TaskDownloadMissingLyricsDescription": "Спампоўвае тэксты для песняў",
"TaskExtractMediaSegments": "Сканіраванне медыя-сегмента",
"TaskMoveTrickplayImages": "Перанесці месцазнаходжанне выявы Trickplay",
"CleanupUserDataTask": "Задача па ачыстцы дадзеных карыстальніка",
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 20f38de62f..52a26c1af2 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -137,5 +137,6 @@
"TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.",
"TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay",
"TaskMoveTrickplayImagesDescription": "Mueve archivos de trickplay existentes según la configuración de la biblioteca.",
- "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario"
+ "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario",
+ "CleanupUserDataTaskDescription": "Limpia toda la información de usuario (Estado de última vez visto, favoritos, etc) del archivo media que no está presente por los últimos 90 días."
}
diff --git a/Emby.Server.Implementations/Localization/Core/es_DO.json b/Emby.Server.Implementations/Localization/Core/es_DO.json
index 8cdd06b7c4..f98a5e5b2c 100644
--- a/Emby.Server.Implementations/Localization/Core/es_DO.json
+++ b/Emby.Server.Implementations/Localization/Core/es_DO.json
@@ -125,5 +125,11 @@
"Undefined": "Sin definir",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.",
- "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad."
+ "TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad.",
+ "NotificationOptionApplicationUpdateAvailable": "actualización disponible",
+ "TaskDownloadMissingLyrics": "Descargue letras desaparecidas",
+ "TaskDownloadMissingLyricsDescription": "Decarga letras para canciones",
+ "TaskMoveTrickplayImages": "Mover localización de foto vista previa",
+ "NotificationOptionApplicationUpdateInstalled": "Aplicación actualización disponible",
+ "CleanupUserDataTask": "Tarea de limpieza de los datos del usuario"
}
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index b95d07d5cf..f847d83d14 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -12,10 +12,10 @@
"DeviceOfflineWithName": "{0} wurde getrennt",
"DeviceOnlineWithName": "{0} ist verbunden",
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
- "Favorites": "Favoriten",
+ "Favorites": "Favorite",
"Folders": "Ordner",
"Genres": "Genre",
- "HeaderAlbumArtists": "Album-Künstler",
+ "HeaderAlbumArtists": "Album-Künschtler",
"HeaderContinueWatching": "weiter schauen",
"HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblings-Künstler",
diff --git a/Emby.Server.Implementations/Localization/Core/ht.json b/Emby.Server.Implementations/Localization/Core/ht.json
index 4fcba99e90..f927d3173a 100644
--- a/Emby.Server.Implementations/Localization/Core/ht.json
+++ b/Emby.Server.Implementations/Localization/Core/ht.json
@@ -1,3 +1,62 @@
{
- "Books": "liv"
+ "Books": "Liv",
+ "TasksLibraryCategory": "Libreri",
+ "Albums": "Albòm yo",
+ "Artists": "Atis yo",
+ "Application": "Aplikasyon",
+ "Channels": "Kanal yo",
+ "ChapterNameValue": "Chapit {0}",
+ "Default": "Defo",
+ "DeviceOnlineWithName": "{0} konekte",
+ "DeviceOfflineWithName": "{0} dekonekte",
+ "External": "Extèn",
+ "Collections": "Koleksyon yo",
+ "Favorites": "Pi Renmen",
+ "Folders": "Dosye",
+ "Genres": "Jan yo",
+ "Forced": "Fòse",
+ "HeaderAlbumArtists": "Albòm Atis",
+ "HeaderContinueWatching": "Kontinye Kade",
+ "HeaderFavoriteAlbums": "Albòm Pi Renmen",
+ "HeaderFavoriteArtists": "Atis Pi Renmen",
+ "HeaderFavoriteEpisodes": "Epizòd Pi Renmen",
+ "HeaderFavoriteShows": "Emisyon Pi Renmen",
+ "HeaderFavoriteSongs": "Mizik Pi Renmen",
+ "HeaderLiveTV": "Televizyon an Direk",
+ "HeaderNextUp": "Pwochen an",
+ "HomeVideos": "Videyo Lakay",
+ "Latest": "Pi Resan",
+ "MessageApplicationUpdated": "Sèvè Jellyfin met a jou",
+ "MessageApplicationUpdatedTo": "Sèvè Jellyfin met a jou sou {0}",
+ "Movies": "Fim",
+ "MixedContent": "Kontni Melanje",
+ "Music": "Mizik",
+ "MusicVideos": "Videyo Mizik",
+ "NameInstallFailed": "{0} enstalasyon fe fayit",
+ "NameSeasonNumber": "Sezon {0}",
+ "NameSeasonUnknown": "Sezon Enkoni",
+ "NotificationOptionCameraImageUploaded": "Imaj Kamera telechaje",
+ "NotificationOptionInstallationFailed": "Enstalasyon echwe",
+ "Photos": "Foto",
+ "PluginInstalledWithName": "{0} te enstale",
+ "PluginUninstalledWithName": "{0} te dezenstale",
+ "PluginUpdatedWithName": "{0} te mi a jou",
+ "ScheduledTaskFailedWithName": "{0} echwe",
+ "ScheduledTaskStartedWithName": "{0} komanse",
+ "Songs": "Mizik yo",
+ "Shows": "Emisyon yo",
+ "System": "Sistèm",
+ "TvShows": "Emisyon Tele",
+ "User": "Itilizatè",
+ "UserCreatedWithName": "Itilizatè {0} kreye",
+ "UserDeletedWithName": "Itilizatè {0} a efase",
+ "UserDownloadingItemWithValues": "{0} ap telechaje {1}",
+ "UserOfflineFromDevice": "{0} dekonekte de {1}",
+ "UserStartedPlayingItemWithValues": "{0} ap jwe {1} sou {2}",
+ "UserStoppedPlayingItemWithValues": "{0} fin jwe {1} sou {2}",
+ "UserPasswordChangedWithName": "Modpas la chanje pou Itilizatè {0}",
+ "ValueSpecialEpisodeName": "Spesyal - {0}",
+ "VersionNumber": "Vesyon {0}",
+ "TasksApplicationCategory": "Aplikasyon",
+ "TasksMaintenanceCategory": "Antretyen"
}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 8baa63d89f..e73c56cb90 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -136,5 +136,7 @@
"TaskExtractMediaSegments": "Skann mediasegment",
"TaskMoveTrickplayImages": "Migrer bildeplassering for Trickplay",
"TaskMoveTrickplayImagesDescription": "Flytter eksisterende Trickplay-filer i henhold til biblioteksinstillingene.",
- "TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment."
+ "TaskExtractMediaSegmentsDescription": "Trekker ut eller henter mediasegmenter fra plugins som støtter MediaSegment.",
+ "CleanupUserDataTaskDescription": "Sletter all brukerdata (avspillings-status, favoritter osv.) fra innhold som har vært utilgjengelig i minst 90 dager.",
+ "CleanupUserDataTask": "Oppgave for opprydding av brukerdata"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
index f7d1b112e1..9076b9c878 100644
--- a/Emby.Server.Implementations/Localization/Core/pr.json
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -43,5 +43,75 @@
"NameInstallFailed": "Ye couldn't bring {0} aboard yer ship",
"MessageApplicationUpdatedTo": "Yer Map of the Seas has been scribbled with {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Yer Map Drawer has been rescribbled to {0}",
- "MessageServerConfigurationUpdated": "Yer Map drawer has been rescribbled"
+ "MessageServerConfigurationUpdated": "Yer Map drawer has been rescribbled",
+ "Inherit": "Carry on what be passed along",
+ "Latest": "Newfangled",
+ "Movies": "Moving pictures",
+ "NewVersionIsAvailable": "A fresh build o’ Jellyfin Server be waitin’ fer ye to fetch.",
+ "NotificationOptionPluginInstalled": "Plugin nailed down",
+ "NotificationOptionVideoPlayback": "Video playback be underway",
+ "ScheduledTaskFailedWithName": "{0} ran aground",
+ "StartupEmbyServerIsLoading": "Jellyfin Server be preparin’ the ship. Try yer luck again soon.",
+ "UserOfflineFromDevice": "{0} severed ties with {1}",
+ "UserDownloadingItemWithValues": "{0} be haulin’ in {1}",
+ "UserStartedPlayingItemWithValues": "{0} be playin’ {1} aboard {2}",
+ "ValueHasBeenAddedToLibrary": "{0} be stashed in yer treasure trove",
+ "TaskCleanCacheDescription": "Wipes away cache cargo no longer called fer.",
+ "TaskCleanLogsDescription": "Clears the logbook o’ entries older than {0} days.",
+ "TaskRefreshPeopleDescription": "Refreshes the charts fer actors an’ directors in yer Treasure Trove.",
+ "UserLockedOutWithName": "Matey {0} be denied boarding",
+ "TaskAudioNormalization": "Steadyin’ the shanties",
+ "TaskAudioNormalizationDescription": "Scans files fer shanty steadiyin’ data.",
+ "HeaderRecordingGroups": "Loggin' Groups",
+ "MusicVideos": "Shanty films",
+ "Playlists": "Lists o’ plunder",
+ "Plugin": "Extra sail",
+ "NotificationOptionVideoPlaybackStopped": "Video playback dropped anchor",
+ "NameSeasonNumber": "Saga {0}",
+ "NameSeasonUnknown": "Saga be Lost",
+ "NotificationOptionApplicationUpdateAvailable": "A fresh build awaits",
+ "NotificationOptionApplicationUpdateInstalled": "App upgrade be aboard",
+ "NotificationOptionAudioPlayback": "Audio playback be rollin",
+ "NotificationOptionAudioPlaybackStopped": "Audio playback dropped anchor",
+ "NotificationOptionCameraImageUploaded": "Spyglass shot be hoisted",
+ "NotificationOptionInstallationFailed": "Install be wrecked",
+ "NotificationOptionNewLibraryContent": "Fresh plunder ready to claim",
+ "NotificationOptionPluginError": "Plugin ran aground",
+ "NotificationOptionPluginUninstalled": "Plugin cast overboard",
+ "NotificationOptionPluginUpdateInstalled": "Plugin patched ‘n ready",
+ "NotificationOptionServerRestartRequired": "Server be due fer a restart",
+ "NotificationOptionTaskFailed": "Set chore went overboard",
+ "TaskRefreshLibraryDescription": "Searches the Treasure Trove fer new plunder ‘n updates the charts.",
+ "PluginInstalledWithName": "{0} nailed down",
+ "TaskCleanLogs": "Swab the Log Hold",
+ "TaskRefreshPeople": "Freshen the Mateys",
+ "PluginUninstalledWithName": "{0} sent t’ Davy Jones",
+ "PluginUpdatedWithName": "{0} patched ‘n ready",
+ "ProviderValue": "Supplier o’ goods: {0}",
+ "ScheduledTaskStartedWithName": "{0} set sail",
+ "ServerNameNeedsToBeRestarted": "{0} be cravin’ a restart",
+ "Shows": "Sagas",
+ "SubtitleDownloadFailureFromForItem": "Subtitles be sunk fetchin’ from {0} fer {1}",
+ "Sync": "Match the tides",
+ "System": "The ship’s works",
+ "TvShows": "TV Sagas",
+ "Undefined": "Uncharted",
+ "User": "Matey",
+ "UserCreatedWithName": "Matey {0} joined the crew",
+ "UserDeletedWithName": "Matey {0} cast overboard",
+ "UserOnlineFromDevice": "{0} be aboard ship from {1}",
+ "UserPasswordChangedWithName": "New passphrase set fer Matey {0}",
+ "UserPolicyUpdatedWithName": "Ship rules be changed fer {0}",
+ "UserStoppedPlayingItemWithValues": "{0} be done playin’ {1} on {2",
+ "ValueSpecialEpisodeName": "Special Tale – {0}",
+ "VersionNumber": "Edition {0}",
+ "TasksMaintenanceCategory": "Hull patchin’",
+ "TasksLibraryCategory": "Treasure Trove",
+ "TasksApplicationCategory": "Ship",
+ "TaskCleanActivityLog": "Clear the Ship’s Log",
+ "TaskCleanActivityLogDescription": "Purges ship’s logs older than the chosen time.",
+ "TaskCleanCache": "Sweep the Cache Chest",
+ "TaskRefreshChapterImages": "Claim chapter portraits",
+ "TaskRefreshChapterImagesDescription": "Paints wee portraits fer videos that own chapters.",
+ "TaskRefreshLibrary": "Scan the Treasure Trove"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 84be91a872..1470a538c2 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -70,7 +70,7 @@
"ScheduledTaskFailedWithName": "{0} - неудачна",
"ScheduledTaskStartedWithName": "{0} - запущена",
"ServerNameNeedsToBeRestarted": "Необходим перезапуск {0}",
- "Shows": "Телешоу",
+ "Shows": "Сериалы",
"Songs": "Композиции",
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
diff --git a/Emby.Server.Implementations/Localization/Core/sr.json b/Emby.Server.Implementations/Localization/Core/sr.json
index af40b5e5a9..76a136cf56 100644
--- a/Emby.Server.Implementations/Localization/Core/sr.json
+++ b/Emby.Server.Implementations/Localization/Core/sr.json
@@ -126,5 +126,16 @@
"HearingImpaired": "ослабљен слух",
"TaskAudioNormalization": "Нормализација звука",
"TaskCleanCollectionsAndPlaylists": "Очистите колекције и плејлисте",
- "TaskAudioNormalizationDescription": "Скенира датотеке за податке о нормализацији звука."
+ "TaskAudioNormalizationDescription": "Скенира датотеке за податке о нормализацији звука.",
+ "TaskRefreshTrickplayImages": "Направи сличице за визуелно премотавање",
+ "TaskRefreshTrickplayImagesDescription": "Прављење сличица које помажу код визуелног премотавања видео-снимака.",
+ "TaskDownloadMissingLyrics": "Преузми стихове који недостају",
+ "TaskCleanCollectionsAndPlaylistsDescription": "Уклања ставке које више не постоје из колекција и плејлиста.",
+ "TaskExtractMediaSegments": "Скенирај сегменте медија",
+ "TaskExtractMediaSegmentsDescription": "Извлачи или добавља сегменте медија у додацима који раде са MediaSegment-ом.",
+ "TaskMoveTrickplayImagesDescription": "Премешта постојеће сличице за визуелно премотавање сходно подешавањима библиотеке.",
+ "CleanupUserDataTask": "Задатак чишћења корисничких података",
+ "CleanupUserDataTaskDescription": "Чисти све корисничке податке (напредак гледања, ознаке за омиљено...) медија који нису доступни 90 дана или дуже.",
+ "TaskMoveTrickplayImages": "Промени локацију сличица за визуелно премотавање",
+ "TaskDownloadMissingLyricsDescription": "Преузми стихове песама"
}
diff --git a/Emby.Server.Implementations/Localization/iso6392.txt b/Emby.Server.Implementations/Localization/iso6392.txt
index 5e65bae26f..d5a7e866b8 100644
--- a/Emby.Server.Implementations/Localization/iso6392.txt
+++ b/Emby.Server.Implementations/Localization/iso6392.txt
@@ -402,8 +402,8 @@ sog|||Sogdian|sogdien
som||so|Somali|somali
son|||Songhai languages|songhai, langues
sot||st|Sotho, Southern|sotho du Sud
-spa||es-419|Spanish; Latin|espagnol; Latin
spa||es|Spanish; Castilian|espagnol; castillan
+spa||es-419|Spanish; Latin|espagnol; Latin
sqi|alb|sq|Albanian|albanais
srd||sc|Sardinian|sarde
srn|||Sranan Tongo|sranan tongo
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 1ce363de5c..c9d76df0bf 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -314,7 +314,7 @@ namespace Emby.Server.Implementations.Playlists
return;
}
- var newPriorItemIndex = newIndex > oldIndexAccessible ? newIndex : newIndex - 1 < 0 ? 0 : newIndex - 1;
+ var newPriorItemIndex = Math.Max(newIndex - 1, 0);
var newPriorItemId = accessibleChildren[newPriorItemIndex].Item1.ItemId;
var newPriorItemIndexOnAllChildren = children.FindIndex(c => c.Item1.ItemId.Equals(newPriorItemId));
var adjustedNewIndex = DetermineAdjustedIndex(newPriorItemIndexOnAllChildren, newIndex);
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
index bf8ffaf479..92d7a3907a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs
@@ -61,7 +61,7 @@ public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask
yield return new TaskTriggerInfo
{
Type = TaskTriggerInfoType.IntervalTrigger,
- IntervalTicks = TimeSpan.FromHours(24).Ticks
+ IntervalTicks = TimeSpan.FromHours(6).Ticks
};
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
index 18162ad2fc..6e4e5c7808 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
@@ -1,10 +1,14 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Database.Implementations;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
+using Microsoft.EntityFrameworkCore;
namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
@@ -15,16 +19,19 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
+ private readonly IDbContextFactory<JellyfinDbContext> _dbContextFactory;
/// <summary>
/// Initializes a new instance of the <see cref="PeopleValidationTask" /> class.
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
- public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization)
+ /// <param name="dbContextFactory">Instance of the <see cref="IDbContextFactory{TContext}"/> interface.</param>
+ public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization, IDbContextFactory<JellyfinDbContext> dbContextFactory)
{
_libraryManager = libraryManager;
_localization = localization;
+ _dbContextFactory = dbContextFactory;
}
/// <inheritdoc />
@@ -62,8 +69,61 @@ public class PeopleValidationTask : IScheduledTask, IConfigurableScheduledTask
}
/// <inheritdoc />
- public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+ public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
- return _libraryManager.ValidatePeopleAsync(progress, cancellationToken);
+ IProgress<double> subProgress = new Progress<double>((val) => progress.Report(val / 2));
+ await _libraryManager.ValidatePeopleAsync(subProgress, cancellationToken).ConfigureAwait(false);
+
+ subProgress = new Progress<double>((val) => progress.Report((val / 2) + 50));
+ var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
+ await using (context.ConfigureAwait(false))
+ {
+ var dupQuery = context.Peoples
+ .GroupBy(e => new { e.Name, e.PersonType })
+ .Where(e => e.Count() > 1)
+ .Select(e => e.Select(f => f.Id).ToArray());
+
+ var total = dupQuery.Count();
+
+ const int PartitionSize = 100;
+ var iterator = 0;
+ int itemCounter;
+ var buffer = ArrayPool<Guid[]>.Shared.Rent(PartitionSize)!;
+ try
+ {
+ do
+ {
+ itemCounter = 0;
+ await foreach (var item in dupQuery
+ .Take(PartitionSize)
+ .AsAsyncEnumerable()
+ .WithCancellation(cancellationToken)
+ .ConfigureAwait(false))
+ {
+ buffer[itemCounter++] = item;
+ }
+
+ for (int i = 0; i < itemCounter; i++)
+ {
+ var item = buffer[i];
+ var reference = item[0];
+ var dups = item[1..];
+ await context.PeopleBaseItemMap.WhereOneOrMany(dups, e => e.PeopleId)
+ .ExecuteUpdateAsync(e => e.SetProperty(f => f.PeopleId, reference), cancellationToken)
+ .ConfigureAwait(false);
+ await context.Peoples.Where(e => dups.Contains(e.Id)).ExecuteDeleteAsync(cancellationToken).ConfigureAwait(false);
+ subProgress.Report(100f / total * ((iterator * PartitionSize) + i));
+ }
+
+ iterator++;
+ } while (itemCounter == PartitionSize && !cancellationToken.IsCancellationRequested);
+ }
+ finally
+ {
+ ArrayPool<Guid[]>.Shared.Return(buffer);
+ }
+
+ subProgress.Report(100);
+ }
}
}
diff --git a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs
index 01c1e596f9..86d08ed27b 100644
--- a/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs
+++ b/Emby.Server.Implementations/Sorting/IsFavoriteOrLikeComparer.cs
@@ -6,7 +6,6 @@ using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
@@ -54,7 +53,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
- return x.IsFavoriteOrLiked(User) ? 0 : 1;
+ return x.IsFavoriteOrLiked(User, userItemData: null) ? 0 : 1;
}
}
}
diff --git a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs
index 6f206c8772..9faa02f1fd 100644
--- a/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/IsPlayedComparer.cs
@@ -7,7 +7,6 @@ using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
@@ -55,7 +54,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
- return x.IsPlayed(User) ? 0 : 1;
+ return x.IsPlayed(User, userItemData: null) ? 0 : 1;
}
}
}
diff --git a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs
index fd1326327b..6f177c4637 100644
--- a/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs
+++ b/Emby.Server.Implementations/Sorting/IsUnplayedComparer.cs
@@ -7,7 +7,6 @@ using Jellyfin.Database.Implementations.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
-using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Sorting
{
@@ -55,7 +54,7 @@ namespace Emby.Server.Implementations.Sorting
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
- return x.IsUnplayed(User) ? 0 : 1;
+ return x.IsUnplayed(User, userItemData: null) ? 0 : 1;
}
}
}