208 lines
6.8 KiB
C#

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);
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
if (filter.Limit > 0)
{
dbQuery = dbQuery.Take(filter.Limit);
}
return dbQuery.Select(e => e.Name).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];
query = query
.Where(e => context.BaseItems.Any(b => b.Type == personType && b.Name == e.Name && b.UserData!.Any(u => u.IsFavorite == filter.IsFavorite && u.UserId.Equals(filter.User.Id))));
}
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);
}
}