mirror of
				https://github.com/Kareadita/Kavita.git
				synced 2025-11-04 03:27:05 -05:00 
			
		
		
		
	* Expand the list of potential favicon icons to grab. * Added a url mapping functionality to use alternative urls for fetching icons * Initial commit to streamline media encoding. No DB migration yet, No UI changes, no Task changes. * Started refactoring code so that webp queries use encoding format instead. * More refactoring to remove hardcoded webp references. * Moved manual migrations to their own folder to keep things organized. Manually drop the obsolete webp keys. * Removed old apis for converting media and now have one. Reworked where the conversion code was located and streamlined events and whatnot. * Make favicon encode setting aware * Cleaned up favicon conversion * Updated format counter to now just use Extension from MangaFile now that it's been out a while. * Tweaked jumpbar code to reduce a lookup to hashmap. * Added AVIF (8-bit only) support. * In UpdatePeopleList, use FirstOrDefault as Single adds extra checks that may not be needed. * You can now remove weblinks from edit series page and you can leave empty cells, they will just be removed on backend. * Forgot a file * Don't prompt to write a review, just show the pencil. It's the same amount of clicks if you do, less if you dont. * Fixed Refresh token using wrong Claim to look up the user. * Refactored how we refresh authentication to perform it every 10 m ins to ensure we always stay authenticated. * Changed Version update code to run more throughout the day. Updated some hangfire to newer method signatures.
		
			
				
	
	
		
			379 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			379 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
using System;
 | 
						|
using System.Collections.Generic;
 | 
						|
using System.Globalization;
 | 
						|
using System.Linq;
 | 
						|
using System.Threading.Tasks;
 | 
						|
using API.DTOs;
 | 
						|
using API.DTOs.Filtering;
 | 
						|
using API.DTOs.JumpBar;
 | 
						|
using API.DTOs.Metadata;
 | 
						|
using API.Entities;
 | 
						|
using API.Entities.Enums;
 | 
						|
using API.Extensions;
 | 
						|
using API.Extensions.QueryExtensions;
 | 
						|
using AutoMapper;
 | 
						|
using AutoMapper.QueryableExtensions;
 | 
						|
using Kavita.Common.Extensions;
 | 
						|
using Microsoft.EntityFrameworkCore;
 | 
						|
 | 
						|
namespace API.Data.Repositories;
 | 
						|
 | 
						|
[Flags]
 | 
						|
public enum LibraryIncludes
 | 
						|
{
 | 
						|
    None = 1,
 | 
						|
    Series = 2,
 | 
						|
    AppUser = 4,
 | 
						|
    Folders = 8,
 | 
						|
    // Ratings = 16
 | 
						|
}
 | 
						|
 | 
						|
public interface ILibraryRepository
 | 
						|
{
 | 
						|
    void Add(Library library);
 | 
						|
    void Update(Library library);
 | 
						|
    void Delete(Library? library);
 | 
						|
    Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync();
 | 
						|
    Task<bool> LibraryExists(string libraryName);
 | 
						|
    Task<Library?> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes = LibraryIncludes.None);
 | 
						|
    Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName);
 | 
						|
    Task<IEnumerable<Library>> GetLibrariesAsync(LibraryIncludes includes = LibraryIncludes.None);
 | 
						|
    Task<IEnumerable<Library>> GetLibrariesForUserIdAsync(int userId);
 | 
						|
    IEnumerable<int> GetLibraryIdsForUserIdAsync(int userId, QueryContext queryContext = QueryContext.None);
 | 
						|
    Task<LibraryType> GetLibraryTypeAsync(int libraryId);
 | 
						|
    Task<IEnumerable<Library>> GetLibraryForIdsAsync(IEnumerable<int> libraryIds, LibraryIncludes includes = LibraryIncludes.None);
 | 
						|
    Task<int> GetTotalFiles();
 | 
						|
    IEnumerable<JumpKeyDto> GetJumpBarAsync(int libraryId);
 | 
						|
    Task<IList<AgeRatingDto>> GetAllAgeRatingsDtosForLibrariesAsync(List<int> libraryIds);
 | 
						|
    Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int> libraryIds);
 | 
						|
    Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync();
 | 
						|
    IEnumerable<PublicationStatusDto> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds);
 | 
						|
    Task<bool> DoAnySeriesFoldersMatch(IEnumerable<string> folders);
 | 
						|
    Task<string?> GetLibraryCoverImageAsync(int libraryId);
 | 
						|
    Task<IList<string>> GetAllCoverImagesAsync();
 | 
						|
    Task<IDictionary<int, LibraryType>> GetLibraryTypesForIdsAsync(IEnumerable<int> libraryIds);
 | 
						|
    Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat);
 | 
						|
}
 | 
						|
 | 
						|
public class LibraryRepository : ILibraryRepository
 | 
						|
{
 | 
						|
    private readonly DataContext _context;
 | 
						|
    private readonly IMapper _mapper;
 | 
						|
 | 
						|
    public LibraryRepository(DataContext context, IMapper mapper)
 | 
						|
    {
 | 
						|
        _context = context;
 | 
						|
        _mapper = mapper;
 | 
						|
    }
 | 
						|
 | 
						|
    public void Add(Library library)
 | 
						|
    {
 | 
						|
        _context.Library.Add(library);
 | 
						|
    }
 | 
						|
 | 
						|
    public void Update(Library library)
 | 
						|
    {
 | 
						|
        _context.Entry(library).State = EntityState.Modified;
 | 
						|
    }
 | 
						|
 | 
						|
    public void Delete(Library? library)
 | 
						|
    {
 | 
						|
        if (library == null) return;
 | 
						|
        _context.Library.Remove(library);
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName)
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .Include(l => l.AppUsers)
 | 
						|
            .Where(library => library.AppUsers.Any(x => x.UserName == userName))
 | 
						|
            .OrderBy(l => l.Name)
 | 
						|
            .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
 | 
						|
            .AsNoTracking()
 | 
						|
            .AsSingleQuery()
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Returns all libraries including their AppUsers + extra includes
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="includes"></param>
 | 
						|
    /// <returns></returns>
 | 
						|
    public async Task<IEnumerable<Library>> GetLibrariesAsync(LibraryIncludes includes = LibraryIncludes.None)
 | 
						|
    {
 | 
						|
        var query = _context.Library
 | 
						|
            .Include(l => l.AppUsers)
 | 
						|
            .Select(l => l);
 | 
						|
 | 
						|
        query = AddIncludesToQuery(query, includes);
 | 
						|
        return await query.ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// This does not track
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="userId"></param>
 | 
						|
    /// <returns></returns>
 | 
						|
    public async Task<IEnumerable<Library>> GetLibrariesForUserIdAsync(int userId)
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .Include(l => l.AppUsers)
 | 
						|
            .Where(l => l.AppUsers.Select(ap => ap.Id).Contains(userId))
 | 
						|
            .AsNoTracking()
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public IEnumerable<int> GetLibraryIdsForUserIdAsync(int userId, QueryContext queryContext = QueryContext.None)
 | 
						|
    {
 | 
						|
        return _context.Library
 | 
						|
            .IsRestricted(queryContext)
 | 
						|
            .Where(l => l.AppUsers.Select(ap => ap.Id).Contains(userId))
 | 
						|
            .Select(l => l.Id)
 | 
						|
            .AsEnumerable();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<LibraryType> GetLibraryTypeAsync(int libraryId)
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .Where(l => l.Id == libraryId)
 | 
						|
            .AsNoTracking()
 | 
						|
            .Select(l => l.Type)
 | 
						|
            .SingleAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IEnumerable<Library>> GetLibraryForIdsAsync(IEnumerable<int> libraryIds, LibraryIncludes includes = LibraryIncludes.None)
 | 
						|
    {
 | 
						|
        var query = _context.Library
 | 
						|
            .Where(x => libraryIds.Contains(x.Id));
 | 
						|
 | 
						|
        AddIncludesToQuery(query, includes);
 | 
						|
            return await query.ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<int> GetTotalFiles()
 | 
						|
    {
 | 
						|
        return await _context.MangaFile.CountAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public IEnumerable<JumpKeyDto> GetJumpBarAsync(int libraryId)
 | 
						|
    {
 | 
						|
        var seriesSortCharacters = _context.Series.Where(s => s.LibraryId == libraryId)
 | 
						|
            .Select(s => s.SortName!.ToUpper())
 | 
						|
            .OrderBy(s => s)
 | 
						|
            .AsEnumerable()
 | 
						|
            .Select(s => s[0]);
 | 
						|
 | 
						|
        // Map the title to the number of entities
 | 
						|
        var firstCharacterMap = new Dictionary<char, int>();
 | 
						|
        foreach (var sortChar in seriesSortCharacters)
 | 
						|
        {
 | 
						|
            var c = sortChar;
 | 
						|
            var isAlpha = char.IsLetter(sortChar);
 | 
						|
            if (!isAlpha) c = '#';
 | 
						|
            firstCharacterMap.TryAdd(c, 0);
 | 
						|
 | 
						|
            firstCharacterMap[c] += 1;
 | 
						|
        }
 | 
						|
 | 
						|
        return firstCharacterMap.Keys.Select(k => new JumpKeyDto()
 | 
						|
        {
 | 
						|
            Key = k + string.Empty,
 | 
						|
            Size = firstCharacterMap[k],
 | 
						|
            Title = k + string.Empty
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Returns all Libraries with their Folders
 | 
						|
    /// </summary>
 | 
						|
    /// <returns></returns>
 | 
						|
    public async Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync()
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .Include(f => f.Folders)
 | 
						|
            .OrderBy(l => l.Name)
 | 
						|
            .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
 | 
						|
            .AsSplitQuery()
 | 
						|
            .AsNoTracking()
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<Library?> GetLibraryForIdAsync(int libraryId, LibraryIncludes includes = LibraryIncludes.None)
 | 
						|
    {
 | 
						|
 | 
						|
        var query = _context.Library
 | 
						|
            .Where(x => x.Id == libraryId);
 | 
						|
 | 
						|
        query = AddIncludesToQuery(query, includes);
 | 
						|
        return await query.SingleOrDefaultAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    private static IQueryable<Library> AddIncludesToQuery(IQueryable<Library> query, LibraryIncludes includeFlags)
 | 
						|
    {
 | 
						|
        if (includeFlags.HasFlag(LibraryIncludes.Folders))
 | 
						|
        {
 | 
						|
            query = query.Include(l => l.Folders);
 | 
						|
        }
 | 
						|
 | 
						|
        if (includeFlags.HasFlag(LibraryIncludes.Series))
 | 
						|
        {
 | 
						|
            query = query.Include(l => l.Series);
 | 
						|
        }
 | 
						|
 | 
						|
        if (includeFlags.HasFlag(LibraryIncludes.AppUser))
 | 
						|
        {
 | 
						|
            query = query.Include(l => l.AppUsers);
 | 
						|
        }
 | 
						|
 | 
						|
        return query.AsSplitQuery();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<bool> LibraryExists(string libraryName)
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .AsNoTracking()
 | 
						|
            .AnyAsync(x => x.Name != null && x.Name.Equals(libraryName));
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IEnumerable<LibraryDto>> GetLibrariesForUserAsync(AppUser user)
 | 
						|
    {
 | 
						|
        return await _context.Library
 | 
						|
            .Where(library => library.AppUsers.Contains(user))
 | 
						|
            .Include(l => l.Folders)
 | 
						|
            .AsNoTracking()
 | 
						|
            .AsSplitQuery()
 | 
						|
            .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    public async Task<IList<AgeRatingDto>> GetAllAgeRatingsDtosForLibrariesAsync(List<int> libraryIds)
 | 
						|
    {
 | 
						|
        return await _context.Series
 | 
						|
            .Where(s => libraryIds.Contains(s.LibraryId))
 | 
						|
            .Select(s => s.Metadata.AgeRating)
 | 
						|
            .Distinct()
 | 
						|
            .Select(s => new AgeRatingDto()
 | 
						|
            {
 | 
						|
                Value = s,
 | 
						|
                Title = s.ToDescription()
 | 
						|
            })
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync(List<int> libraryIds)
 | 
						|
    {
 | 
						|
        var ret = await _context.Series
 | 
						|
            .Where(s => libraryIds.Contains(s.LibraryId))
 | 
						|
            .Select(s => s.Metadata.Language)
 | 
						|
            .AsSplitQuery()
 | 
						|
            .AsNoTracking()
 | 
						|
            .Distinct()
 | 
						|
            .ToListAsync();
 | 
						|
 | 
						|
        return ret
 | 
						|
            .Where(s => !string.IsNullOrEmpty(s))
 | 
						|
            .Select(s => new LanguageDto()
 | 
						|
            {
 | 
						|
                Title = CultureInfo.GetCultureInfo(s).DisplayName,
 | 
						|
                IsoCode = s
 | 
						|
            })
 | 
						|
            .OrderBy(s => s.Title)
 | 
						|
            .ToList();
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IList<LanguageDto>> GetAllLanguagesForLibrariesAsync()
 | 
						|
    {
 | 
						|
        var ret = await _context.Series
 | 
						|
            .Select(s => s.Metadata.Language)
 | 
						|
            .AsSplitQuery()
 | 
						|
            .AsNoTracking()
 | 
						|
            .Distinct()
 | 
						|
            .ToListAsync();
 | 
						|
 | 
						|
        return ret
 | 
						|
            .Where(s => !string.IsNullOrEmpty(s))
 | 
						|
            .Select(s => new LanguageDto()
 | 
						|
            {
 | 
						|
                Title = CultureInfo.GetCultureInfo(s).DisplayName,
 | 
						|
                IsoCode = s
 | 
						|
            })
 | 
						|
            .OrderBy(s => s.Title)
 | 
						|
            .ToList();
 | 
						|
    }
 | 
						|
 | 
						|
    public IEnumerable<PublicationStatusDto> GetAllPublicationStatusesDtosForLibrariesAsync(List<int> libraryIds)
 | 
						|
    {
 | 
						|
        return  _context.Series
 | 
						|
            .Where(s => libraryIds.Contains(s.LibraryId))
 | 
						|
            .AsSplitQuery()
 | 
						|
            .Select(s => s.Metadata.PublicationStatus)
 | 
						|
            .Distinct()
 | 
						|
            .AsEnumerable()
 | 
						|
            .Select(s => new PublicationStatusDto()
 | 
						|
            {
 | 
						|
                Value = s,
 | 
						|
                Title = s.ToDescription()
 | 
						|
            })
 | 
						|
            .OrderBy(s => s.Title);
 | 
						|
    }
 | 
						|
 | 
						|
    /// <summary>
 | 
						|
    /// Checks if any series folders match the folders passed in
 | 
						|
    /// </summary>
 | 
						|
    /// <param name="folders"></param>
 | 
						|
    /// <returns></returns>
 | 
						|
    public async Task<bool> DoAnySeriesFoldersMatch(IEnumerable<string> folders)
 | 
						|
    {
 | 
						|
        var normalized = folders.Select(Services.Tasks.Scanner.Parser.Parser.NormalizePath);
 | 
						|
        return await _context.Series.AnyAsync(s => normalized.Contains(s.FolderPath));
 | 
						|
    }
 | 
						|
 | 
						|
    public Task<string?> GetLibraryCoverImageAsync(int libraryId)
 | 
						|
    {
 | 
						|
        return _context.Library
 | 
						|
            .Where(l => l.Id == libraryId)
 | 
						|
            .Select(l => l.CoverImage)
 | 
						|
            .SingleOrDefaultAsync();
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IList<string>> GetAllCoverImagesAsync()
 | 
						|
    {
 | 
						|
        return (await _context.ReadingList
 | 
						|
            .Select(t => t.CoverImage)
 | 
						|
            .Where(t => !string.IsNullOrEmpty(t))
 | 
						|
            .ToListAsync())!;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IDictionary<int, LibraryType>> GetLibraryTypesForIdsAsync(IEnumerable<int> libraryIds)
 | 
						|
    {
 | 
						|
        var types = await _context.Library
 | 
						|
            .Where(l => libraryIds.Contains(l.Id))
 | 
						|
            .AsNoTracking()
 | 
						|
            .Select(l => new
 | 
						|
            {
 | 
						|
                LibraryId = l.Id,
 | 
						|
                LibraryType = l.Type
 | 
						|
            })
 | 
						|
            .ToListAsync();
 | 
						|
 | 
						|
        var dict = new Dictionary<int, LibraryType>();
 | 
						|
 | 
						|
        foreach (var type in types)
 | 
						|
        {
 | 
						|
            dict.TryAdd(type.LibraryId, type.LibraryType);
 | 
						|
        }
 | 
						|
 | 
						|
        return dict;
 | 
						|
    }
 | 
						|
 | 
						|
    public async Task<IList<Library>> GetAllWithCoversInDifferentEncoding(EncodeFormat encodeFormat)
 | 
						|
    {
 | 
						|
        var extension = encodeFormat.GetExtension();
 | 
						|
        return await _context.Library
 | 
						|
            .Where(c => !string.IsNullOrEmpty(c.CoverImage) && !c.CoverImage.EndsWith(extension))
 | 
						|
            .ToListAsync();
 | 
						|
    }
 | 
						|
}
 |