Report Media Issues (#1964)
* Started working on a report problems implementation. * Started code * Added logging to book and archive service. * Removed an additional ComicInfo read when comicinfo is null when trying to load. But we've already done it once earlier, so there really isn't any point. * Added basic implementation for media errors. * MediaErrors will ignore duplicate errors when there are multiple issues on same file in a scan. * Fixed unit tests * Basic code in place to view and clear. Just UI Cleanup needed. * Slight css upgrade * Fixed up centering and simplified the code to use regular array instead of observables as it wasn't working. * Fixed unit tests * Fixed unit tests for real
This commit is contained in:
parent
642b23ed61
commit
d1e4878345
32 changed files with 2586 additions and 57 deletions
|
@ -44,13 +44,16 @@ public class ArchiveService : IArchiveService
|
|||
private readonly ILogger<ArchiveService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IImageService _imageService;
|
||||
private readonly IMediaErrorService _mediaErrorService;
|
||||
private const string ComicInfoFilename = "ComicInfo.xml";
|
||||
|
||||
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService, IImageService imageService)
|
||||
public ArchiveService(ILogger<ArchiveService> logger, IDirectoryService directoryService,
|
||||
IImageService imageService, IMediaErrorService mediaErrorService)
|
||||
{
|
||||
_logger = logger;
|
||||
_directoryService = directoryService;
|
||||
_imageService = imageService;
|
||||
_mediaErrorService = mediaErrorService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -120,6 +123,8 @@ public class ArchiveService : IArchiveService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[GetNumberOfPagesFromArchive] There was an exception when reading archive stream: {ArchivePath}. Defaulting to 0 pages", archivePath);
|
||||
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService,
|
||||
"This archive cannot be read or not supported", ex);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -238,6 +243,8 @@ public class ArchiveService : IArchiveService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[GetCoverImage] There was an exception when reading archive stream: {ArchivePath}. Defaulting to no cover image", archivePath);
|
||||
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService,
|
||||
"This archive cannot be read or not supported", ex);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
@ -403,6 +410,8 @@ public class ArchiveService : IArchiveService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[GetComicInfo] There was an exception when reading archive stream: {Filepath}", archivePath);
|
||||
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService,
|
||||
"This archive cannot be read or not supported", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -485,9 +494,11 @@ public class ArchiveService : IArchiveService
|
|||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(e, "[ExtractArchive] There was a problem extracting {ArchivePath} to {ExtractPath}",archivePath, extractPath);
|
||||
_logger.LogWarning(ex, "[ExtractArchive] There was a problem extracting {ArchivePath} to {ExtractPath}",archivePath, extractPath);
|
||||
_mediaErrorService.ReportMediaIssue(archivePath, MediaErrorProducer.ArchiveService,
|
||||
"This archive cannot be read or not supported", ex);
|
||||
throw new KavitaException(
|
||||
$"There was an error when extracting {archivePath}. Check the file exists, has read permissions or the server OS can support all path characters.");
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ public class BookService : IBookService
|
|||
private readonly ILogger<BookService> _logger;
|
||||
private readonly IDirectoryService _directoryService;
|
||||
private readonly IImageService _imageService;
|
||||
private readonly IMediaErrorService _mediaErrorService;
|
||||
private readonly StylesheetParser _cssParser = new ();
|
||||
private static readonly RecyclableMemoryStreamManager StreamManager = new ();
|
||||
private const string CssScopeClass = ".book-content";
|
||||
|
@ -72,11 +73,12 @@ public class BookService : IBookService
|
|||
}
|
||||
};
|
||||
|
||||
public BookService(ILogger<BookService> logger, IDirectoryService directoryService, IImageService imageService)
|
||||
public BookService(ILogger<BookService> logger, IDirectoryService directoryService, IImageService imageService, IMediaErrorService mediaErrorService)
|
||||
{
|
||||
_logger = logger;
|
||||
_directoryService = directoryService;
|
||||
_imageService = imageService;
|
||||
_mediaErrorService = mediaErrorService;
|
||||
}
|
||||
|
||||
private static bool HasClickableHrefPart(HtmlNode anchor)
|
||||
|
@ -394,6 +396,8 @@ public class BookService : IBookService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "There was an error reading css file for inlining likely due to a key mismatch in metadata");
|
||||
await _mediaErrorService.ReportMediaIssueAsync(book.FilePath, MediaErrorProducer.BookService,
|
||||
"There was an error reading css file for inlining likely due to a key mismatch in metadata", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -480,7 +484,9 @@ public class BookService : IBookService
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[GetComicInfo] There was an exception getting metadata");
|
||||
_logger.LogWarning(ex, "[GetComicInfo] There was an exception parsing metadata");
|
||||
_mediaErrorService.ReportMediaIssue(filePath, MediaErrorProducer.BookService,
|
||||
"There was an exception parsing metadata", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -553,6 +559,8 @@ public class BookService : IBookService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[BookService] There was an exception getting number of pages, defaulting to 0");
|
||||
_mediaErrorService.ReportMediaIssue(filePath, MediaErrorProducer.BookService,
|
||||
"There was an exception getting number of pages, defaulting to 0", ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -697,6 +705,8 @@ public class BookService : IBookService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[BookService] There was an exception when opening epub book: {FileName}", filePath);
|
||||
_mediaErrorService.ReportMediaIssue(filePath, MediaErrorProducer.BookService,
|
||||
"There was an exception when opening epub book", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -916,8 +926,9 @@ public class BookService : IBookService
|
|||
}
|
||||
} catch (Exception ex)
|
||||
{
|
||||
// NOTE: We can log this to media analysis service
|
||||
_logger.LogError(ex, "There was an issue reading one of the pages for {Book}", book.FilePath);
|
||||
await _mediaErrorService.ReportMediaIssueAsync(book.FilePath, MediaErrorProducer.BookService,
|
||||
"There was an issue reading one of the pages for", ex);
|
||||
}
|
||||
|
||||
throw new KavitaException("Could not find the appropriate html for that page");
|
||||
|
@ -990,6 +1001,8 @@ public class BookService : IBookService
|
|||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "[BookService] There was a critical error and prevented thumbnail generation on {BookFile}. Defaulting to no cover image", fileFilePath);
|
||||
_mediaErrorService.ReportMediaIssue(fileFilePath, MediaErrorProducer.BookService,
|
||||
"There was a critical error and prevented thumbnail generation", ex);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
@ -1014,6 +1027,8 @@ public class BookService : IBookService
|
|||
_logger.LogWarning(ex,
|
||||
"[BookService] There was a critical error and prevented thumbnail generation on {BookFile}. Defaulting to no cover image",
|
||||
fileFilePath);
|
||||
_mediaErrorService.ReportMediaIssue(fileFilePath, MediaErrorProducer.BookService,
|
||||
"There was a critical error and prevented thumbnail generation", ex);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
|
67
API/Services/MediaErrorService.cs
Normal file
67
API/Services/MediaErrorService.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Helpers.Builders;
|
||||
using Hangfire;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
public enum MediaErrorProducer
|
||||
{
|
||||
BookService = 0,
|
||||
ArchiveService = 1
|
||||
|
||||
}
|
||||
|
||||
public interface IMediaErrorService
|
||||
{
|
||||
Task ReportMediaIssueAsync(string filename, MediaErrorProducer producer, string errorMessage, string details);
|
||||
void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, string details);
|
||||
Task ReportMediaIssueAsync(string filename, MediaErrorProducer producer, string errorMessage, Exception ex);
|
||||
void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, Exception ex);
|
||||
}
|
||||
|
||||
public class MediaErrorService : IMediaErrorService
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public MediaErrorService(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task ReportMediaIssueAsync(string filename, MediaErrorProducer producer, string errorMessage, Exception ex)
|
||||
{
|
||||
await ReportMediaIssueAsync(filename, producer, errorMessage, ex.Message);
|
||||
}
|
||||
|
||||
public void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, Exception ex)
|
||||
{
|
||||
// To avoid overhead on commits, do async. We don't need to wait.
|
||||
BackgroundJob.Enqueue(() => ReportMediaIssueAsync(filename, producer, errorMessage, ex.Message));
|
||||
}
|
||||
|
||||
public void ReportMediaIssue(string filename, MediaErrorProducer producer, string errorMessage, string details)
|
||||
{
|
||||
// To avoid overhead on commits, do async. We don't need to wait.
|
||||
BackgroundJob.Enqueue(() => ReportMediaIssueAsync(filename, producer, errorMessage, details));
|
||||
}
|
||||
|
||||
public async Task ReportMediaIssueAsync(string filename, MediaErrorProducer producer, string errorMessage, string details)
|
||||
{
|
||||
var error = new MediaErrorBuilder(filename)
|
||||
.WithComment(errorMessage)
|
||||
.WithDetails(details)
|
||||
.Build();
|
||||
|
||||
if (await _unitOfWork.MediaErrorRepository.ExistsAsync(error))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.MediaErrorRepository.Attach(error);
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
}
|
|
@ -399,6 +399,7 @@ public class TaskScheduler : ITaskScheduler
|
|||
|
||||
var scheduledJobs = JobStorage.Current.GetMonitoringApi().ScheduledJobs(0, int.MaxValue);
|
||||
ret = scheduledJobs.Any(j =>
|
||||
j.Value.Job != null &&
|
||||
j.Value.Job.Method.DeclaringType != null && j.Value.Job.Args.SequenceEqual(args) &&
|
||||
j.Value.Job.Method.Name.Equals(methodName) &&
|
||||
j.Value.Job.Method.DeclaringType.Name.Equals(className));
|
||||
|
|
|
@ -657,19 +657,13 @@ public class ProcessSeries : IProcessSeries
|
|||
}
|
||||
}
|
||||
|
||||
public void UpdateChapterFromComicInfo(Chapter chapter, ComicInfo? info)
|
||||
public void UpdateChapterFromComicInfo(Chapter chapter, ComicInfo? comicInfo)
|
||||
{
|
||||
if (comicInfo == null) return;
|
||||
var firstFile = chapter.Files.MinBy(x => x.Chapter);
|
||||
if (firstFile == null ||
|
||||
_cacheHelper.IsFileUnmodifiedSinceCreationOrLastScan(chapter, false, firstFile)) return;
|
||||
|
||||
var comicInfo = info;
|
||||
if (info == null)
|
||||
{
|
||||
comicInfo = _readingItemService.GetComicInfo(firstFile.FilePath);
|
||||
}
|
||||
|
||||
if (comicInfo == null) return;
|
||||
_logger.LogTrace("[ScannerService] Read ComicInfo for {File}", firstFile.FilePath);
|
||||
|
||||
chapter.AgeRating = ComicInfo.ConvertAgeRatingToEnum(comicInfo.AgeRating);
|
||||
|
@ -807,11 +801,15 @@ public class ProcessSeries : IProcessSeries
|
|||
private static IList<string> GetTagValues(string comicInfoTagSeparatedByComma)
|
||||
{
|
||||
// TODO: Move this to an extension and test it
|
||||
if (!string.IsNullOrEmpty(comicInfoTagSeparatedByComma))
|
||||
if (string.IsNullOrEmpty(comicInfoTagSeparatedByComma))
|
||||
{
|
||||
return comicInfoTagSeparatedByComma.Split(",").Select(s => s.Trim()).DistinctBy(Parser.Parser.Normalize).ToList();
|
||||
return ImmutableList<string>.Empty;
|
||||
}
|
||||
return ImmutableList<string>.Empty;
|
||||
|
||||
return comicInfoTagSeparatedByComma.Split(",")
|
||||
.Select(s => s.Trim())
|
||||
.DistinctBy(Parser.Parser.Normalize)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue