aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server.Implementations/Item/PeopleRepository.cs')
-rw-r--r--Jellyfin.Server.Implementations/Item/PeopleRepository.cs212
1 files changed, 212 insertions, 0 deletions
diff --git a/Jellyfin.Server.Implementations/Item/PeopleRepository.cs b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
new file mode 100644
index 000000000..be58e2a52
--- /dev/null
+++ b/Jellyfin.Server.Implementations/Item/PeopleRepository.cs
@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Jellyfin.Data.Enums;
+using Jellyfin.Database.Implementations;
+using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Entities.Libraries;
+using Jellyfin.Extensions;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Persistence;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Server.Implementations.Item;
+#pragma warning disable RS0030 // Do not use banned APIs
+#pragma warning disable CA1304 // Specify CultureInfo
+#pragma warning disable CA1311 // Specify a culture or use an invariant version
+#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
+
+/// <summary>
+/// Manager for handling people.
+/// </summary>
+/// <param name="dbProvider">Efcore Factory.</param>
+/// <param name="itemTypeLookup">Items lookup service.</param>
+/// <remarks>
+/// Initializes a new instance of the <see cref="PeopleRepository"/> class.
+/// </remarks>
+public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, IItemTypeLookup itemTypeLookup) : IPeopleRepository
+{
+ private readonly IDbContextFactory<JellyfinDbContext> _dbProvider = dbProvider;
+
+ /// <inheritdoc/>
+ public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery filter)
+ {
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter);
+
+ // dbQuery = dbQuery.OrderBy(e => e.ListOrder);
+ if (filter.Limit > 0)
+ {
+ dbQuery = dbQuery.Take(filter.Limit);
+ }
+
+ // Include PeopleBaseItemMap
+ if (!filter.ItemId.IsEmpty())
+ {
+ dbQuery = dbQuery.Include(p => p.BaseItems!.Where(m => m.ItemId == filter.ItemId));
+ }
+
+ return dbQuery.AsEnumerable().Select(Map).ToArray();
+ }
+
+ /// <inheritdoc/>
+ public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery filter)
+ {
+ using var context = _dbProvider.CreateDbContext();
+ var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct();
+
+ // dbQuery = dbQuery.OrderBy(e => e.ListOrder);
+ if (filter.Limit > 0)
+ {
+ dbQuery = dbQuery.Take(filter.Limit);
+ }
+
+ return dbQuery.ToArray();
+ }
+
+ /// <inheritdoc />
+ public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
+ {
+ using var context = _dbProvider.CreateDbContext();
+
+ // TODO: yes for __SOME__ reason there can be duplicates.
+ people = people.DistinctBy(e => e.Id).ToArray();
+ var personids = people.Select(f => f.Id);
+ var existingPersons = context.Peoples.Where(p => personids.Contains(p.Id)).Select(f => f.Id).ToArray();
+ context.Peoples.AddRange(people.Where(e => !existingPersons.Contains(e.Id)).Select(Map));
+ context.SaveChanges();
+
+ var maps = context.PeopleBaseItemMap.Where(e => e.ItemId == itemId).ToList();
+ foreach (var person in people)
+ {
+ var existingMap = maps.FirstOrDefault(e => e.PeopleId == person.Id);
+ if (existingMap is null)
+ {
+ context.PeopleBaseItemMap.Add(new PeopleBaseItemMap()
+ {
+ Item = null!,
+ ItemId = itemId,
+ People = null!,
+ PeopleId = person.Id,
+ ListOrder = person.SortOrder,
+ SortOrder = person.SortOrder,
+ Role = person.Role
+ });
+ }
+ else
+ {
+ // person mapping already exists so remove from list
+ maps.Remove(existingMap);
+ }
+ }
+
+ context.PeopleBaseItemMap.RemoveRange(maps);
+
+ context.SaveChanges();
+ }
+
+ private PersonInfo Map(People people)
+ {
+ var mapping = people.BaseItems?.FirstOrDefault();
+ var personInfo = new PersonInfo()
+ {
+ Id = people.Id,
+ Name = people.Name,
+ Role = mapping?.Role,
+ SortOrder = mapping?.SortOrder
+ };
+ if (Enum.TryParse<PersonKind>(people.PersonType, out var kind))
+ {
+ personInfo.Type = kind;
+ }
+
+ return personInfo;
+ }
+
+ private People Map(PersonInfo people)
+ {
+ var personInfo = new People()
+ {
+ Name = people.Name,
+ PersonType = people.Type.ToString(),
+ Id = people.Id,
+ };
+
+ return personInfo;
+ }
+
+ private IQueryable<People> TranslateQuery(IQueryable<People> query, JellyfinDbContext context, InternalPeopleQuery filter)
+ {
+ if (filter.User is not null && filter.IsFavorite.HasValue)
+ {
+ var personType = itemTypeLookup.BaseItemKindNames[BaseItemKind.Person];
+ var oldQuery = query;
+
+ query = context.UserData
+ .Where(u => u.Item!.Type == personType && u.IsFavorite == filter.IsFavorite && u.UserId.Equals(filter.User.Id))
+ .Join(oldQuery, e => e.Item!.Name, e => e.Name, (item, person) => person)
+ .Distinct()
+ .AsNoTracking();
+ }
+
+ if (!filter.ItemId.IsEmpty())
+ {
+ query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.ItemId)));
+ }
+
+ if (!filter.AppearsInItemId.IsEmpty())
+ {
+ query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.AppearsInItemId)));
+ }
+
+ var queryPersonTypes = filter.PersonTypes.Where(IsValidPersonType).ToList();
+ if (queryPersonTypes.Count > 0)
+ {
+ query = query.Where(e => queryPersonTypes.Contains(e.PersonType));
+ }
+
+ var queryExcludePersonTypes = filter.ExcludePersonTypes.Where(IsValidPersonType).ToList();
+
+ if (queryExcludePersonTypes.Count > 0)
+ {
+ query = query.Where(e => !queryPersonTypes.Contains(e.PersonType));
+ }
+
+ if (filter.MaxListOrder.HasValue && !filter.ItemId.IsEmpty())
+ {
+ query = query.Where(e => e.BaseItems!.First(w => w.ItemId == filter.ItemId).ListOrder <= filter.MaxListOrder.Value);
+ }
+
+ if (!string.IsNullOrWhiteSpace(filter.NameContains))
+ {
+ var nameContainsUpper = filter.NameContains.ToUpper();
+ query = query.Where(e => e.Name.ToUpper().Contains(nameContainsUpper));
+ }
+
+ return query;
+ }
+
+ private bool IsAlphaNumeric(string str)
+ {
+ if (string.IsNullOrWhiteSpace(str))
+ {
+ return false;
+ }
+
+ for (int i = 0; i < str.Length; i++)
+ {
+ if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool IsValidPersonType(string value)
+ {
+ return IsAlphaNumeric(value);
+ }
+}