Holiday Changes (#1706)
* Fixed a bug on bookmark mode not finding correct image for prefetcher. * Fixed up the edit series relationship modal on tablet viewports. * On double page mode, only bookmark 1 page if only 1 pages is renderered on screen. * Added percentage read of a given library and average hours read per week to user stats. * Fixed a bug in the reader with paging in bookmark mode * Added a "This Week" option to top readers history * Added date ranges for reading time. Added dates that don't have anything, but might remove. * On phone, when applying a metadata filter, when clicking apply, collapse the filter automatically. * Disable jump bar and the resuming from last spot when a custom sort is applied. * Ensure all Regex.Replace or Matches have timeouts set
This commit is contained in:
parent
13c1787165
commit
7b51fdfb2e
26 changed files with 224 additions and 76 deletions
|
|
@ -108,16 +108,17 @@ public class StatsController : BaseApiController
|
|||
/// Returns reading history events for a give or all users, broken up by day, and format
|
||||
/// </summary>
|
||||
/// <param name="userId">If 0, defaults to all users, else just userId</param>
|
||||
/// <param name="days">If 0, defaults to all time, else just those days asked for</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("reading-count-by-day")]
|
||||
[ResponseCache(CacheProfileName = "Statistics")]
|
||||
public async Task<ActionResult<IEnumerable<PagesReadOnADayCount<DateTime>>>> ReadCountByDay(int userId = 0)
|
||||
public async Task<ActionResult<IEnumerable<PagesReadOnADayCount<DateTime>>>> ReadCountByDay(int userId = 0, int days = 0)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var isAdmin = User.IsInRole(PolicyConstants.AdminRole);
|
||||
if (!isAdmin && userId != user.Id) return BadRequest();
|
||||
|
||||
return Ok(await _statService.ReadCountByDay(userId));
|
||||
return Ok(await _statService.ReadCountByDay(userId, days));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,12 +13,9 @@ public class UserReadStatistics
|
|||
/// Total time spent reading based on estimates
|
||||
/// </summary>
|
||||
public long TimeSpentReading { get; set; }
|
||||
/// <summary>
|
||||
/// A list of genres mapped with genre and number of series that fall into said genre
|
||||
/// </summary>
|
||||
public ICollection<Tuple<string, long>> FavoriteGenres { get; set; }
|
||||
|
||||
public long ChaptersRead { get; set; }
|
||||
public DateTime LastActive { get; set; }
|
||||
public long AvgHoursPerWeekSpentReading { get; set; }
|
||||
public double AvgHoursPerWeekSpentReading { get; set; }
|
||||
public IEnumerable<StatCount<float>> PercentReadPerLibrary { get; set; }
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,7 +322,7 @@ public class SeriesRepository : ISeriesRepository
|
|||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
|
||||
var justYear = Regex.Match(searchQuery, @"\d{4}").Value;
|
||||
var justYear = Regex.Match(searchQuery, @"\d{4}", RegexOptions.None, Services.Tasks.Scanner.Parser.Parser.RegexTimeout).Value;
|
||||
var hasYearInQuery = !string.IsNullOrEmpty(justYear);
|
||||
var yearComparison = hasYearInQuery ? int.Parse(justYear) : 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -535,8 +535,8 @@ public class BookService : IBookService
|
|||
|
||||
private static string EscapeTags(string content)
|
||||
{
|
||||
content = Regex.Replace(content, @"<script(.*)(/>)", "<script$1></script>");
|
||||
content = Regex.Replace(content, @"<title(.*)(/>)", "<title$1></title>");
|
||||
content = Regex.Replace(content, @"<script(.*)(/>)", "<script$1></script>", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
content = Regex.Replace(content, @"<title(.*)(/>)", "<title$1></title>", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
return content;
|
||||
}
|
||||
|
||||
|
|
@ -995,12 +995,12 @@ public class BookService : IBookService
|
|||
}
|
||||
|
||||
// Remove comments from CSS
|
||||
body = Regex.Replace(body, @"/\*[\d\D]*?\*/", string.Empty);
|
||||
body = Regex.Replace(body, @"/\*[\d\D]*?\*/", string.Empty, RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
|
||||
body = Regex.Replace(body, @"[a-zA-Z]+#", "#");
|
||||
body = Regex.Replace(body, @"[\n\r]+\s*", string.Empty);
|
||||
body = Regex.Replace(body, @"\s+", " ");
|
||||
body = Regex.Replace(body, @"\s?([:,;{}])\s?", "$1");
|
||||
body = Regex.Replace(body, @"[a-zA-Z]+#", "#", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
body = Regex.Replace(body, @"[\n\r]+\s*", string.Empty, RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
body = Regex.Replace(body, @"\s+", " ", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
body = Regex.Replace(body, @"\s?([:,;{}])\s?", "$1", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
try
|
||||
{
|
||||
body = body.Replace(";}", "}");
|
||||
|
|
@ -1010,7 +1010,7 @@ public class BookService : IBookService
|
|||
//Swallow exception. Some css don't have style rules ending in ';'
|
||||
}
|
||||
|
||||
body = Regex.Replace(body, @"([\s:]0)(px|pt|%|em)", "$1");
|
||||
body = Regex.Replace(body, @"([\s:]0)(px|pt|%|em)", "$1", RegexOptions.None, Tasks.Scanner.Parser.Parser.RegexTimeout);
|
||||
|
||||
|
||||
return body;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public interface IStatisticService
|
|||
Task<FileExtensionBreakdownDto> GetFileBreakdown();
|
||||
Task<IEnumerable<TopReadDto>> GetTopUsers(int days);
|
||||
Task<IEnumerable<ReadHistoryEvent>> GetReadingHistory(int userId);
|
||||
Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0);
|
||||
Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0, int days = 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -81,7 +81,34 @@ public class StatisticService : IStatisticService
|
|||
.Select(p => p.LastModified)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
//var
|
||||
// Reading Progress by Library Name
|
||||
|
||||
// First get the total pages per library
|
||||
var totalPageCountByLibrary = _context.Chapter
|
||||
.Join(_context.Volume, c => c.VolumeId, v => v.Id, (chapter, volume) => new { chapter, volume })
|
||||
.Join(_context.Series, g => g.volume.SeriesId, s => s.Id, (g, series) => new { g.chapter, series })
|
||||
.AsEnumerable()
|
||||
.GroupBy(g => g.series.LibraryId)
|
||||
.ToDictionary(g => g.Key, g => g.Sum(c => c.chapter.Pages));
|
||||
//
|
||||
//
|
||||
var totalProgressByLibrary = await _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId)
|
||||
.Where(p => p.LibraryId > 0)
|
||||
.GroupBy(p => p.LibraryId)
|
||||
.Select(g => new StatCount<float>
|
||||
{
|
||||
Count = g.Key,
|
||||
Value = g.Sum(p => p.PagesRead) / (float) totalPageCountByLibrary[g.Key]
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
|
||||
var averageReadingTimePerWeek = _context.AppUserProgresses
|
||||
.Where(p => p.AppUserId == userId)
|
||||
.Join(_context.Chapter, p => p.ChapterId, c => c.Id,
|
||||
(p, c) => (p.PagesRead / (float) c.Pages) * c.AvgHoursToRead)
|
||||
.Average() / 7;
|
||||
|
||||
return new UserReadStatistics()
|
||||
{
|
||||
|
|
@ -89,6 +116,8 @@ public class StatisticService : IStatisticService
|
|||
TimeSpentReading = timeSpentReading,
|
||||
ChaptersRead = chaptersRead,
|
||||
LastActive = lastActive,
|
||||
PercentReadPerLibrary = totalProgressByLibrary,
|
||||
AvgHoursPerWeekSpentReading = averageReadingTimePerWeek
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +326,7 @@ public class StatisticService : IStatisticService
|
|||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0)
|
||||
public async Task<IEnumerable<PagesReadOnADayCount<DateTime>>> ReadCountByDay(int userId = 0, int days = 0)
|
||||
{
|
||||
var query = _context.AppUserProgresses
|
||||
.AsSplitQuery()
|
||||
|
|
@ -314,7 +343,13 @@ public class StatisticService : IStatisticService
|
|||
query = query.Where(x => x.appUserProgresses.AppUserId == userId);
|
||||
}
|
||||
|
||||
return await query.GroupBy(x => new
|
||||
if (days > 0)
|
||||
{
|
||||
var date = DateTime.Now.AddDays(days * -1);
|
||||
query = query.Where(x => x.appUserProgresses.LastModified >= date && x.appUserProgresses.Created >= date);
|
||||
}
|
||||
|
||||
var results = await query.GroupBy(x => new
|
||||
{
|
||||
Day = x.appUserProgresses.Created.Date,
|
||||
x.series.Format
|
||||
|
|
@ -327,6 +362,23 @@ public class StatisticService : IStatisticService
|
|||
})
|
||||
.OrderBy(d => d.Value)
|
||||
.ToListAsync();
|
||||
|
||||
if (results.Count > 0)
|
||||
{
|
||||
var minDay = results.Min(d => d.Value);
|
||||
for (var date = minDay; date < DateTime.Now; date = date.AddDays(1))
|
||||
{
|
||||
if (results.Any(d => d.Value == date)) continue;
|
||||
results.Add(new PagesReadOnADayCount<DateTime>()
|
||||
{
|
||||
Format = MangaFormat.Unknown,
|
||||
Value = date,
|
||||
Count = 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TopReadDto>> GetTopUsers(int days)
|
||||
|
|
|
|||
|
|
@ -911,7 +911,7 @@ public static class Parser
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$"))
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$", MatchOptions, RegexTimeout))
|
||||
{
|
||||
return (float) 0.0;
|
||||
}
|
||||
|
|
@ -929,7 +929,7 @@ public static class Parser
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$"))
|
||||
if (!Regex.IsMatch(range, @"^[\d\-.]+$", MatchOptions, RegexTimeout))
|
||||
{
|
||||
return (float) 0.0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue