Swipe Issues (#1745)
* Updated theme support to be able to customize the tile color dynamically from a theme via --tile-color. In addition, --theme-color will update apple-mobile-web-app-status-bar-style as well as the non-apple variants * Removed --manga-reader-bg-color as it wasn't used anywhere. Fixed double pagination on swipe. * Cleaned up some dead threshold code for swipe. * Started refactoring tests to use an abstract test class. Stopping because I should do on the .net 7 branch to avoid large merge conflicts. Tests need to be re-designed so they can run in parallel. * Fixed a bug in reading lists where when deleting an item, order could be miscalculated. * Started adding new information for stat service. Refactored time spent reading to be more accurate by taking average time against how much of the chapter the user has read. * Hooked up total time reading at server stat level. Don't show fancy graphs on mobile. * Added new stats for v0.7 * Added a test for Clearing want to read * Fixed a few tests that weren't resetting state between runs * Fixed some broken unit tests * Ensure all Series queries sort by a case invariant string. * Added more aggressive caching of images. This will result in a min delay on pages after a cover is changed. * Fixed a bug where if during new word count calculation, new word count is zero, restoring the old count wasn't working. * Cleaned up some of the code for getting time estimates * Fixed a bug where triggering swipe right wasn't working when there was no scroll * Delete the temp folder for creating a download after a full zip is created.
This commit is contained in:
parent
3d6de68089
commit
549e52b458
26 changed files with 488 additions and 339 deletions
|
|
@ -32,7 +32,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="chapterId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("chapter-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId"})]
|
||||
public async Task<ActionResult> GetChapterCoverImage(int chapterId)
|
||||
{
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ChapterRepository.GetChapterCoverImageAsync(chapterId));
|
||||
|
|
@ -48,7 +48,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="libraryId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("library-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"libraryId"})]
|
||||
public async Task<ActionResult> GetLibraryCoverImage(int libraryId)
|
||||
{
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.LibraryRepository.GetLibraryCoverImageAsync(libraryId));
|
||||
|
|
@ -64,7 +64,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="volumeId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("volume-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"volumeId"})]
|
||||
public async Task<ActionResult> GetVolumeCoverImage(int volumeId)
|
||||
{
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.VolumeRepository.GetVolumeCoverImageAsync(volumeId));
|
||||
|
|
@ -79,7 +79,7 @@ public class ImageController : BaseApiController
|
|||
/// </summary>
|
||||
/// <param name="seriesId">Id of Series</param>
|
||||
/// <returns></returns>
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"seriesId"})]
|
||||
[HttpGet("series-cover")]
|
||||
public async Task<ActionResult> GetSeriesCoverImage(int seriesId)
|
||||
{
|
||||
|
|
@ -98,7 +98,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="collectionTagId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("collection-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"collectionTagId"})]
|
||||
public async Task<ActionResult> GetCollectionCoverImage(int collectionTagId)
|
||||
{
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.CollectionTagRepository.GetCoverImageAsync(collectionTagId));
|
||||
|
|
@ -114,7 +114,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="readingListId"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("readinglist-cover")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"readingListId"})]
|
||||
public async Task<ActionResult> GetReadingListCoverImage(int readingListId)
|
||||
{
|
||||
var path = Path.Join(_directoryService.CoverImageDirectory, await _unitOfWork.ReadingListRepository.GetCoverImageAsync(readingListId));
|
||||
|
|
@ -133,7 +133,7 @@ public class ImageController : BaseApiController
|
|||
/// <param name="apiKey">API Key for user. Needed to authenticate request</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("bookmark")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"chapterId", "pageNum", "apiKey"})]
|
||||
public async Task<ActionResult> GetBookmarkImage(int chapterId, int pageNum, string apiKey)
|
||||
{
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
|
|
@ -155,7 +155,7 @@ public class ImageController : BaseApiController
|
|||
/// <returns></returns>
|
||||
[Authorize(Policy="RequireAdminRole")]
|
||||
[HttpGet("cover-upload")]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images)]
|
||||
[ResponseCache(CacheProfileName = ResponseCacheProfiles.Images, VaryByQueryKeys = new []{"filename"})]
|
||||
public ActionResult GetCoverUploadImage(string filename)
|
||||
{
|
||||
if (filename.Contains("..")) return BadRequest("Invalid Filename");
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public class ServerStatisticsDto
|
|||
public long TotalGenres { get; set; }
|
||||
public long TotalTags { get; set; }
|
||||
public long TotalPeople { get; set; }
|
||||
public long TotalReadingTime { get; set; }
|
||||
public IEnumerable<ICount<SeriesDto>> MostReadSeries { get; set; }
|
||||
/// <summary>
|
||||
/// Total users who have started/reading/read per series
|
||||
|
|
|
|||
|
|
@ -145,4 +145,39 @@ public class ServerInfoDto
|
|||
/// </summary>
|
||||
/// <remarks>Introduced in v0.6.0</remarks>
|
||||
public bool UsingRestrictedProfiles { get; set; }
|
||||
/// <summary>
|
||||
/// Number of users using the Emulate Comic Book setting
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public int UsersWithEmulateComicBook { get; set; }
|
||||
/// <summary>
|
||||
/// Percent (0.0-1.0) of libraries with folder watching enabled
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public float PercentOfLibrariesWithFolderWatchingEnabled { get; set; }
|
||||
/// <summary>
|
||||
/// Percent (0.0-1.0) of libraries included in Search
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public float PercentOfLibrariesIncludedInSearch { get; set; }
|
||||
/// <summary>
|
||||
/// Percent (0.0-1.0) of libraries included in Recommended
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public float PercentOfLibrariesIncludedInRecommended { get; set; }
|
||||
/// <summary>
|
||||
/// Percent (0.0-1.0) of libraries included in Dashboard
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public float PercentOfLibrariesIncludedInDashboard { get; set; }
|
||||
/// <summary>
|
||||
/// Total reading hours of all users
|
||||
/// </summary>
|
||||
/// <remarks>Introduced in v0.7.0</remarks>
|
||||
public long TotalReadingHours { get; set; }
|
||||
/// <summary>
|
||||
/// Is the Server saving covers as WebP
|
||||
/// </summary>
|
||||
/// <remarks>Added in v0.7.0</remarks>
|
||||
public bool StoreCoversAsWebP { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
return await _context.Series
|
||||
.Where(s => s.LibraryId == libraryId)
|
||||
.Includes(includes)
|
||||
.OrderBy(s => s.SortName).ToListAsync();
|
||||
.OrderBy(s => s.SortName.ToLower())
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -223,7 +224,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.OrderBy(s => s.SortName);
|
||||
.OrderBy(s => s.SortName.ToLower());
|
||||
|
||||
return await PagedList<Series>.CreateAsync(query, userParams.PageNumber, userParams.PageSize);
|
||||
}
|
||||
|
|
@ -316,7 +317,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.Where(l => libraryIds.Contains(l.Id))
|
||||
.Where(l => EF.Functions.Like(l.Name, $"%{searchQuery}%"))
|
||||
.IsRestricted(QueryContext.Search)
|
||||
.OrderBy(l => l.Name)
|
||||
.OrderBy(l => l.Name.ToLower())
|
||||
.AsSplitQuery()
|
||||
.Take(maxRecords)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
|
|
@ -335,7 +336,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
|| (hasYearInQuery && s.Metadata.ReleaseYear == yearComparison))
|
||||
.RestrictAgainstAgeRestriction(userRating)
|
||||
.Include(s => s.Library)
|
||||
.OrderBy(s => s.SortName)
|
||||
.OrderBy(s => s.SortName.ToLower())
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.Take(maxRecords)
|
||||
|
|
@ -356,7 +357,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
|| EF.Functions.Like(c.NormalizedTitle, $"%{searchQueryNormalized}%"))
|
||||
.Where(c => c.Promoted || isAdmin)
|
||||
.RestrictAgainstAgeRestriction(userRating)
|
||||
.OrderBy(s => s.Title)
|
||||
.OrderBy(s => s.NormalizedTitle)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
.Take(maxRecords)
|
||||
|
|
@ -377,7 +378,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||
.SelectMany(sm => sm.Genres.Where(t => EF.Functions.Like(t.Title, $"%{searchQuery}%")))
|
||||
.AsSplitQuery()
|
||||
.OrderBy(t => t.Title)
|
||||
.OrderBy(t => t.NormalizedTitle)
|
||||
.Distinct()
|
||||
.Take(maxRecords)
|
||||
.ProjectTo<GenreTagDto>(_mapper.ConfigurationProvider)
|
||||
|
|
@ -387,7 +388,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.Where(sm => seriesIds.Contains(sm.SeriesId))
|
||||
.SelectMany(sm => sm.Tags.Where(t => EF.Functions.Like(t.Title, $"%{searchQuery}%")))
|
||||
.AsSplitQuery()
|
||||
.OrderBy(t => t.Title)
|
||||
.OrderBy(t => t.NormalizedTitle)
|
||||
.Distinct()
|
||||
.Take(maxRecords)
|
||||
.ProjectTo<TagDto>(_mapper.ConfigurationProvider)
|
||||
|
|
@ -719,7 +720,8 @@ public class SeriesRepository : ISeriesRepository
|
|||
})
|
||||
.Where(s => s.PagesRead > 0
|
||||
&& s.PagesRead < s.Series.Pages)
|
||||
.Where(d => d.LatestReadDate >= cutoffProgressPoint || d.LastChapterAdded >= cutoffLastAddedPoint).OrderByDescending(s => s.LatestReadDate)
|
||||
.Where(d => d.LatestReadDate >= cutoffProgressPoint || d.LastChapterAdded >= cutoffLastAddedPoint)
|
||||
.OrderByDescending(s => s.LatestReadDate)
|
||||
.ThenByDescending(s => s.LastChapterAdded)
|
||||
.Select(s => s.Series)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
|
|
@ -777,7 +779,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderBy(s => s.SortName),
|
||||
SortField.SortName => query.OrderBy(s => s.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
|
|
@ -790,7 +792,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderByDescending(s => s.SortName),
|
||||
SortField.SortName => query.OrderByDescending(s => s.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
|
|
@ -844,7 +846,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderBy(s => s.SortName),
|
||||
SortField.SortName => query.OrderBy(s => s.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderBy(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderBy(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderBy(s => s.LastChapterAdded),
|
||||
|
|
@ -856,7 +858,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
{
|
||||
query = filter.SortOptions.SortField switch
|
||||
{
|
||||
SortField.SortName => query.OrderByDescending(s => s.SortName),
|
||||
SortField.SortName => query.OrderByDescending(s => s.SortName.ToLower()),
|
||||
SortField.CreatedDate => query.OrderByDescending(s => s.Created),
|
||||
SortField.LastModifiedDate => query.OrderByDescending(s => s.LastModified),
|
||||
SortField.LastChapterAdded => query.OrderByDescending(s => s.LastChapterAdded),
|
||||
|
|
@ -887,7 +889,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.Where(t => t.SeriesMetadatas.Select(s => s.SeriesId).Contains(seriesId))
|
||||
.ProjectTo<CollectionTagDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.OrderBy(t => t.Title)
|
||||
.OrderBy(t => t.Title.ToLower())
|
||||
.AsSplitQuery()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
|
@ -911,7 +913,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ThenInclude(m => m.Series)
|
||||
.SelectMany(c => c.SeriesMetadatas.Select(sm => sm.Series).Where(s => userLibraries.Contains(s.LibraryId)))
|
||||
.OrderBy(s => s.LibraryId)
|
||||
.ThenBy(s => s.SortName)
|
||||
.ThenBy(s => s.SortName.ToLower())
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsSplitQuery()
|
||||
.AsNoTracking();
|
||||
|
|
@ -941,7 +943,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
|
||||
return await _context.Series
|
||||
.Where(s => seriesIds.Contains(s.Id) && allowedLibraries.Contains(s.LibraryId))
|
||||
.OrderBy(s => s.SortName)
|
||||
.OrderBy(s => s.SortName.ToLower())
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSplitQuery()
|
||||
|
|
|
|||
|
|
@ -299,6 +299,8 @@ public class ArchiveService : IArchiveService
|
|||
try
|
||||
{
|
||||
ZipFile.CreateFromDirectory(tempLocation, zipPath);
|
||||
// Remove the folder as we have the zip
|
||||
_directoryService.ClearAndDeleteDirectory(tempLocation);
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -575,39 +575,20 @@ public class ReaderService : IReaderService
|
|||
{
|
||||
var minHours = Math.Max((int) Math.Round((wordCount / MinWordsPerHour)), 0);
|
||||
var maxHours = Math.Max((int) Math.Round((wordCount / MaxWordsPerHour)), 0);
|
||||
if (maxHours < minHours)
|
||||
{
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = maxHours,
|
||||
MaxHours = minHours,
|
||||
AvgHours = (int) Math.Round((wordCount / AvgWordsPerHour))
|
||||
};
|
||||
}
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = minHours,
|
||||
MaxHours = maxHours,
|
||||
MinHours = Math.Min(minHours, maxHours),
|
||||
MaxHours = Math.Max(minHours, maxHours),
|
||||
AvgHours = (int) Math.Round((wordCount / AvgWordsPerHour))
|
||||
};
|
||||
}
|
||||
|
||||
var minHoursPages = Math.Max((int) Math.Round((pageCount / MinPagesPerMinute / 60F)), 0);
|
||||
var maxHoursPages = Math.Max((int) Math.Round((pageCount / MaxPagesPerMinute / 60F)), 0);
|
||||
if (maxHoursPages < minHoursPages)
|
||||
{
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = maxHoursPages,
|
||||
MaxHours = minHoursPages,
|
||||
AvgHours = (int) Math.Round((pageCount / AvgPagesPerMinute / 60F))
|
||||
};
|
||||
}
|
||||
|
||||
return new HourEstimateRangeDto
|
||||
{
|
||||
MinHours = minHoursPages,
|
||||
MaxHours = maxHoursPages,
|
||||
MinHours = Math.Min(minHoursPages, maxHoursPages),
|
||||
MaxHours = Math.Max(minHoursPages, maxHoursPages),
|
||||
AvgHours = (int) Math.Round((pageCount / AvgPagesPerMinute / 60F))
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public class ReadingListService : IReadingListService
|
|||
public async Task<bool> DeleteReadingListItem(UpdateReadingListPosition dto)
|
||||
{
|
||||
var readingList = await _unitOfWork.ReadingListRepository.GetReadingListByIdAsync(dto.ReadingListId);
|
||||
readingList.Items = readingList.Items.Where(r => r.Id != dto.ReadingListItemId).ToList();
|
||||
readingList.Items = readingList.Items.Where(r => r.Id != dto.ReadingListItemId).OrderBy(r => r.Order).ToList();
|
||||
|
||||
var index = 0;
|
||||
foreach (var readingListItem in readingList.Items)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public interface IStatisticService
|
|||
IEnumerable<StatCount<int>> GetPagesReadCountByYear(int userId = 0);
|
||||
IEnumerable<StatCount<int>> GetWordsReadCountByYear(int userId = 0);
|
||||
Task UpdateServerStatistics();
|
||||
Task<long> TimeSpentReadingForUsersAsync(IList<int> userIds, IList<int> libraryIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -62,18 +63,20 @@ public class StatisticService : IStatisticService
|
|||
.Where(p => libraryIds.Contains(p.LibraryId))
|
||||
.SumAsync(p => p.PagesRead);
|
||||
|
||||
var ids = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId)
|
||||
.Where(p => libraryIds.Contains(p.LibraryId))
|
||||
.Where(p => p.PagesRead > 0)
|
||||
.Select(p => new {p.ChapterId, p.SeriesId})
|
||||
.ToListAsync();
|
||||
// var ids = await _context.AppUserProgresses
|
||||
// .Where(p => p.AppUserId == userId)
|
||||
// .Where(p => libraryIds.Contains(p.LibraryId))
|
||||
// .Where(p => p.PagesRead > 0)
|
||||
// .Select(p => new {p.ChapterId, p.SeriesId})
|
||||
// .ToListAsync();
|
||||
|
||||
var chapterIds = ids.Select(id => id.ChapterId);
|
||||
//var chapterIds = ids.Select(id => id.ChapterId);
|
||||
|
||||
var timeSpentReading = await _context.Chapter
|
||||
.Where(c => chapterIds.Contains(c.Id))
|
||||
.SumAsync(c => c.AvgHoursToRead);
|
||||
// var timeSpentReading = await _context.Chapter
|
||||
// .Where(c => chapterIds.Contains(c.Id))
|
||||
// .SumAsync(c => c.AvgHoursToRead);
|
||||
|
||||
var timeSpentReading = await TimeSpentReadingForUsersAsync(new List<int>() {userId}, libraryIds);
|
||||
|
||||
var totalWordsRead = (long) Math.Round(await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId)
|
||||
|
|
@ -275,6 +278,8 @@ public class StatisticService : IStatisticService
|
|||
.Distinct()
|
||||
.Count();
|
||||
|
||||
|
||||
|
||||
return new ServerStatisticsDto()
|
||||
{
|
||||
ChapterCount = await _context.Chapter.CountAsync(),
|
||||
|
|
@ -289,7 +294,8 @@ public class StatisticService : IStatisticService
|
|||
MostActiveLibraries = mostActiveLibrary,
|
||||
MostPopularSeries = mostPopularSeries,
|
||||
MostReadSeries = mostReadSeries,
|
||||
RecentlyRead = recentlyRead
|
||||
RecentlyRead = recentlyRead,
|
||||
TotalReadingTime = await TimeSpentReadingForUsersAsync(ArraySegment<int>.Empty, ArraySegment<int>.Empty)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -483,6 +489,30 @@ public class StatisticService : IStatisticService
|
|||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
public async Task<long> TimeSpentReadingForUsersAsync(IList<int> userIds, IList<int> libraryIds)
|
||||
{
|
||||
var query = _context.AppUserProgresses
|
||||
.AsSplitQuery();
|
||||
|
||||
if (userIds.Any())
|
||||
{
|
||||
query = query.Where(p => userIds.Contains(p.AppUserId));
|
||||
}
|
||||
if (libraryIds.Any())
|
||||
{
|
||||
query = query.Where(p => libraryIds.Contains(p.LibraryId));
|
||||
}
|
||||
|
||||
return (long) Math.Round(await query
|
||||
.Join(_context.Chapter,
|
||||
p => p.ChapterId,
|
||||
c => c.Id,
|
||||
(progress, chapter) => new {chapter, progress})
|
||||
.Where(p => p.chapter.AvgHoursToRead > 0)
|
||||
.SumAsync(p =>
|
||||
p.chapter.AvgHoursToRead * (p.progress.PagesRead / (1.0f * p.chapter.Pages))));
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TopReadDto>> GetTopUsers(int days)
|
||||
{
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ public class WordCountAnalyzerService : IWordCountAnalyzerService
|
|||
|
||||
}
|
||||
|
||||
if (series.WordCount == 0 && series.WordCount != 0) series.WordCount = existingWordCount; // Restore original word count if the file hasn't changed
|
||||
if (series.WordCount == 0 && existingWordCount != 0) series.WordCount = existingWordCount; // Restore original word count if the file hasn't changed
|
||||
var seriesEstimate = _readerService.GetTimeEstimate(series.WordCount, series.Pages, isEpub);
|
||||
series.MinHoursToRead = seriesEstimate.MinHours;
|
||||
series.MaxHoursToRead = seriesEstimate.MaxHours;
|
||||
|
|
|
|||
|
|
@ -25,18 +25,23 @@ public interface IStatsService
|
|||
Task<ServerInfoDto> GetServerInfo();
|
||||
Task SendCancellation();
|
||||
}
|
||||
/// <summary>
|
||||
/// This is for reporting to the stat server
|
||||
/// </summary>
|
||||
public class StatsService : IStatsService
|
||||
{
|
||||
private readonly ILogger<StatsService> _logger;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly DataContext _context;
|
||||
private readonly IStatisticService _statisticService;
|
||||
private const string ApiUrl = "https://stats.kavitareader.com";
|
||||
|
||||
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context)
|
||||
public StatsService(ILogger<StatsService> logger, IUnitOfWork unitOfWork, DataContext context, IStatisticService statisticService)
|
||||
{
|
||||
_logger = logger;
|
||||
_unitOfWork = unitOfWork;
|
||||
_context = context;
|
||||
_statisticService = statisticService;
|
||||
|
||||
FlurlHttp.ConfigureClient(ApiUrl, cli =>
|
||||
cli.Settings.HttpClientFactory = new UntrustedCertClientFactory());
|
||||
|
|
@ -116,6 +121,14 @@ public class StatsService : IStatsService
|
|||
DotnetVersion = Environment.Version.ToString(),
|
||||
IsDocker = new OsInfo().IsDocker,
|
||||
NumOfCores = Math.Max(Environment.ProcessorCount, 1),
|
||||
UsersWithEmulateComicBook = await _context.AppUserPreferences.CountAsync(p => p.EmulateBook),
|
||||
TotalReadingHours = await _statisticService.TimeSpentReadingForUsersAsync(ArraySegment<int>.Empty, ArraySegment<int>.Empty),
|
||||
|
||||
PercentOfLibrariesWithFolderWatchingEnabled = await GetPercentageOfLibrariesWithFolderWatchingEnabled(),
|
||||
PercentOfLibrariesIncludedInRecommended = await GetPercentageOfLibrariesIncludedInRecommended(),
|
||||
PercentOfLibrariesIncludedInDashboard = await GetPercentageOfLibrariesIncludedInDashboard(),
|
||||
PercentOfLibrariesIncludedInSearch = await GetPercentageOfLibrariesIncludedInSearch(),
|
||||
|
||||
HasBookmarks = (await _unitOfWork.UserRepository.GetAllBookmarksAsync()).Any(),
|
||||
NumberOfLibraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).Count(),
|
||||
NumberOfCollections = (await _unitOfWork.CollectionTagRepository.GetAllTagsAsync()).Count(),
|
||||
|
|
@ -127,6 +140,7 @@ public class StatsService : IStatsService
|
|||
TotalPeople = await _unitOfWork.PersonRepository.GetCountAsync(),
|
||||
UsingSeriesRelationships = await GetIfUsingSeriesRelationship(),
|
||||
StoreBookmarksAsWebP = serverSettings.ConvertBookmarkToWebP,
|
||||
StoreCoversAsWebP = serverSettings.ConvertCoverToWebP,
|
||||
MaxSeriesInALibrary = await MaxSeriesInAnyLibrary(),
|
||||
MaxVolumesInASeries = await MaxVolumesInASeries(),
|
||||
MaxChaptersInASeries = await MaxChaptersInASeries(),
|
||||
|
|
@ -190,6 +204,30 @@ public class StatsService : IStatsService
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<float> GetPercentageOfLibrariesWithFolderWatchingEnabled()
|
||||
{
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
return libraries.Count(l => l.FolderWatching) / (1.0f * libraries.Count);
|
||||
}
|
||||
|
||||
private async Task<float> GetPercentageOfLibrariesIncludedInRecommended()
|
||||
{
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
return libraries.Count(l => l.IncludeInRecommended) / (1.0f * libraries.Count);
|
||||
}
|
||||
|
||||
private async Task<float> GetPercentageOfLibrariesIncludedInDashboard()
|
||||
{
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
return libraries.Count(l => l.IncludeInDashboard) / (1.0f * libraries.Count);
|
||||
}
|
||||
|
||||
private async Task<float> GetPercentageOfLibrariesIncludedInSearch()
|
||||
{
|
||||
var libraries = (await _unitOfWork.LibraryRepository.GetLibrariesAsync()).ToList();
|
||||
return libraries.Count(l => l.IncludeInSearch) / (1.0f * libraries.Count);
|
||||
}
|
||||
|
||||
private Task<bool> GetIfUsingSeriesRelationship()
|
||||
{
|
||||
return _context.SeriesRelation.AnyAsync();
|
||||
|
|
@ -242,6 +280,7 @@ public class StatsService : IStatsService
|
|||
return await _context.AppUserPreferences.Select(p => p.PageSplitOption).Distinct().ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
private async Task<IEnumerable<LayoutMode>> AllMangaReaderLayoutModes()
|
||||
{
|
||||
return await _context.AppUserPreferences.Select(p => p.LayoutMode).Distinct().ToListAsync();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue