diff --git a/Marechai/Pages/Admin/Details/MagazineIssue.razor b/Marechai/Pages/Admin/Details/MagazineIssue.razor index f8a87ac1..af0e1f19 100644 --- a/Marechai/Pages/Admin/Details/MagazineIssue.razor +++ b/Marechai/Pages/Admin/Details/MagazineIssue.razor @@ -40,6 +40,8 @@ @inject DocumentPeopleService PeopleService @inject PeopleByMagazineService PeopleByMagazineService @inject DocumentRolesService DocumentRolesService +@inject IFileReaderService FileReaderService +@inject MagazineScansService MagazineScansService @inject NavigationManager NavigationManager @inject IWebHostEnvironment Host @inject IJSRuntime JSRuntime @@ -185,7 +187,7 @@ }
- @if (!_editing) + @if (!_editing && !_editingScan && !_addingScan) { } @@ -196,7 +198,7 @@ } @L["Back to list"]
-@if (!_editing) +@if (!_editing && !_editingScan && !_addingScan) {

@L["People involved in this magazine"]

@@ -357,6 +359,31 @@ } + + + + @if(_scans.Count > 0) + { + foreach (var scan in _scans) + { +
+
+ + + + + + + +
+ + +
+
+
+ } + } + @@ -373,4 +400,998 @@ +} + +@if(_addingScan && !_editing) +{ + @if(!_uploaded) + { + @if(!_uploading) + { + @L["Choose scan file"] +
+ + @L["Scan type"] + + + + @L["Page"] + @L["Unknown (scan page)"] + @if (!_unknownScanPage) + { + + + + @L["Please enter a valid scanned page."] + + + + } + +
+ } + else + { + @L["Uploading..."] +
+
@($"{_progressValue:F2}")%
+
+ } + @if(_uploadError) + { + @_uploadErrorMessage + } + } + else + { +
@string.Format(L["Image format recognized as {0}"], _imageFormat)
+ @if (_allFinished) + { + @L["All finished!"] + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ @L["Extracting Exif information..."] + + @if(_extractExif == true) + { + @L["OK!"] + } + else if(_extractExif == false) + { + @L["Error!"] + } +
+ @L["Moving file to proper place..."] + + @if(_moveFile == true) + { + @L["OK!"] + } + else if(_moveFile == false) + { + @L["Error!"] + } + + @if(_moveFile == true) + { + + + + } +
+ @L["Converting photo to JPEG with an HD resolution (thumbnail)..."] + + @if(_convertJpegHdTh == true) + { + @L["OK!"] + } + else if(_convertJpegHdTh == false) + { + @L["Error!"] + } + + @if(_convertJpegHdTh == true) + { + + + + } +
+ @L["Converting photo to JPEG with a 1440p resolution (thumbnail)..."] + + @if(_convertJpeg1440Th == true) + { + @L["OK!"] + } + else if(_convertJpeg1440Th == false) + { + @L["Error!"] + } + + @if(_convertJpeg1440Th == true) + { + + + + } +
+ @L["Converting photo to JPEG with a 4K resolution (thumbnail)..."] + + @if(_convertJpeg4kTh == true) + { + @L["OK!"] + } + else if(_convertJpeg4kTh == false) + { + @L["Error!"] + } + + @if(_convertJpeg4kTh == true) + { + + + + } +
+ @L["Converting photo to JPEG with an HD resolution..."] + + @if(_convertJpegHd == true) + { + @L["OK!"] + } + else if(_convertJpegHd == false) + { + @L["Error!"] + } + + @if(_convertJpegHd == true) + { + + + + } +
+ @L["Converting photo to JPEG with a 1440p resolution..."] + + @if(_convertJpeg1440 == true) + { + @L["OK!"] + } + else if(_convertJpeg1440 == false) + { + @L["Error!"] + } + + @if(_convertJpeg1440 == true) + { + + + + } +
+ @L["Converting photo to JPEG with a 4K resolution..."] + + @if(_convertJpeg4k == true) + { + @L["OK!"] + } + else if(_convertJpeg4k == false) + { + @L["Error!"] + } + + @if(_convertJpeg4k == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with an HD resolution (thumbnail)..."] + + @if(_convertJp2kHdTh == true) + { + @L["OK!"] + } + else if(_convertJp2kHdTh == false) + { + @L["Error!"] + } + + @if(_convertJp2kHdTh == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with a 1440p resolution (thumbnail)..."] + + @if(_convertJp2k1440Th == true) + { + @L["OK!"] + } + else if(_convertJp2k1440Th == false) + { + @L["Error!"] + } + + @if(_convertJp2k1440Th == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with a 4K resolution (thumbnail)..."] + + @if(_convertJp2k4kTh == true) + { + @L["OK!"] + } + else if(_convertJp2k4kTh == false) + { + @L["Error!"] + } + + @if(_convertJp2k4kTh == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with an HD resolution..."] + + @if(_convertJp2kHd == true) + { + @L["OK!"] + } + else if(_convertJp2kHd == false) + { + @L["Error!"] + } + + @if(_convertJp2kHd == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with a 1440p resolution..."] + + @if(_convertJp2k1440 == true) + { + @L["OK!"] + } + else if(_convertJp2k1440 == false) + { + @L["Error!"] + } + + @if(_convertJp2k1440 == true) + { + + + + } +
+ @L["Converting photo to JPEG2000 with a 4K resolution..."] + + @if(_convertJp2k4k == true) + { + @L["OK!"] + } + else if(_convertJp2k4k == false) + { + @L["Error!"] + } + + @if(_convertJp2k4k == true) + { + + + + } +
+ @L["Converting photo to WebP with an HD resolution (thumbnail)..."] + + @if(_convertWebpHdTh == true) + { + @L["OK!"] + } + else if(_convertWebpHdTh == false) + { + @L["Error!"] + } + + @if(_convertWebpHdTh == true) + { + + + + } +
+ @L["Converting photo to WebP with a 1440p resolution (thumbnail)..."] + + @if(_convertWebp1440Th == true) + { + @L["OK!"] + } + else if(_convertWebp1440Th == false) + { + @L["Error!"] + } + + @if(_convertWebp1440Th == true) + { + + + + } +
+ @L["Converting photo to WebP with a 4K resolution (thumbnail)..."] + + @if(_convertWebp4kTh == true) + { + @L["OK!"] + } + else if(_convertWebp4kTh == false) + { + @L["Error!"] + } + + @if(_convertWebp4kTh == true) + { + + + + } +
+ @L["Converting photo to WebP with an HD resolution..."] + + @if(_convertWebpHd == true) + { + @L["OK!"] + } + else if(_convertWebpHd == false) + { + @L["Error!"] + } + + @if(_convertWebpHd == true) + { + + + + } +
+ @L["Converting photo to WebP with a 1440p resolution..."] + + @if(_convertWebp1440 == true) + { + @L["OK!"] + } + else if(_convertWebp1440 == false) + { + @L["Error!"] + } + + @if(_convertWebp1440 == true) + { + + + + } +
+ @L["Converting photo to WebP with a 4K resolution..."] + + @if(_convertWebp4k == true) + { + @L["OK!"] + } + else if(_convertWebp4k == false) + { + @L["Error!"] + } + + @if(_convertWebp4k == true) + { + + + + } +
+ @L["Converting photo to HEIF with an HD resolution (thumbnail)..."] + + @if(_convertHeifHdTh == true) + { + @L["OK!"] + } + else if(_convertHeifHdTh == false) + { + @L["Error!"] + } + + @if(_convertHeifHdTh == true) + { + + + + } +
+ @L["Converting photo to HEIF with a 1440p resolution (thumbnail)..."] + + @if(_convertHeif1440Th == true) + { + @L["OK!"] + } + else if(_convertHeif1440Th == false) + { + @L["Error!"] + } + + @if(_convertHeif1440Th == true) + { + + + + } +
+ @L["Converting photo to HEIF with a 4K resolution (thumbnail)..."] + + @if(_convertHeif4kTh == true) + { + @L["OK!"] + } + else if(_convertHeif4kTh == false) + { + @L["Error!"] + } + + @if(_convertHeif4kTh == true) + { + + + + } +
+ @L["Converting photo to HEIF with an HD resolution..."] + + @if(_convertHeifHd == true) + { + @L["OK!"] + } + else if(_convertHeifHd == false) + { + @L["Error!"] + } + + @if(_convertHeifHd == true) + { + + + + } +
+ @L["Converting photo to HEIF with a 1440p resolution..."] + + @if(_convertHeif1440 == true) + { + @L["OK!"] + } + else if(_convertHeif1440 == false) + { + @L["Error!"] + } + + @if(_convertHeif1440 == true) + { + + + + } +
+ @L["Converting photo to HEIF with a 4K resolution..."] + + @if(_convertHeif4k == true) + { + @L["OK!"] + } + else if(_convertHeif4k == false) + { + @L["Error!"] + } + + @if(_convertHeif4k == true) + { + + + + } +
+ @L["Converting photo to AV1F with an HD resolution (thumbnail)..."] + + @if(_convertAvifHdTh == true) + { + @L["OK!"] + } + else if(_convertAvifHdTh == false) + { + @L["Error!"] + } + + @if(_convertAvifHdTh == true) + { + + + + } +
+ @L["Converting photo to AV1F with a 1440p resolution (thumbnail)..."] + + @if(_convertAvif1440Th == true) + { + @L["OK!"] + } + else if(_convertAvif1440Th == false) + { + @L["Error!"] + } + + @if(_convertAvif1440Th == true) + { + + + + } +
+ @L["Converting photo to AV1F with a 4K resolution (thumbnail)..."] + + @if(_convertAvif4kTh == true) + { + @L["OK!"] + } + else if(_convertAvif4kTh == false) + { + @L["Error!"] + } + + @if(_convertAvif4kTh == true) + { + + + + } +
+ @L["Converting photo to AV1F with an HD resolution..."] + + @if(_convertAvifHd == true) + { + @L["OK!"] + } + else if(_convertAvifHd == false) + { + @L["Error!"] + } + + @if(_convertAvifHd == true) + { + + + + } +
+ @L["Converting photo to AV1F with a 1440p resolution..."] + + @if(_convertAvif1440 == true) + { + @L["OK!"] + } + else if(_convertAvif1440 == false) + { + @L["Error!"] + } + + @if(_convertAvif1440 == true) + { + + + + } +
+ @L["Converting photo to AV1F with a 4K resolution..."] + + @if(_convertAvif4k == true) + { + @L["OK!"] + } + else if(_convertAvif4k == false) + { + @L["Error!"] + } + + @if(_convertAvif4k == true) + { + + + + } +
+ @L["Adding photo to database..."] + + @if(_addToDatabase == true) + { + @L["OK!"] + } + else if(_addToDatabase == false) + { + @L["Error!"] + } + +
+ } +} + + +@if(_editingScan && !_editing) +{ +
+ + @L["Uploaded by"] + ")"/> + + + @L["Uploaded date"] + + + + @L["Scan type"] + + + + @L["Page"] + @L["Unknown (scan page)"] + @if (!_unknownScanPage) + { + + + + @L["Please enter a valid scanned page."] + + + + } + + + @L["Author"] + @L["Unknown (author)"] + @if (!_unknownScanAuthor) + { + + + + @L["Please enter a valid author."] + + + + } + + + @L["Scanner manufacturer"] + @L["Unknown (scanner manufacturer)"] + @if (!_unknownScanScannerManufacturer) + { + + + + @L["Please enter a valid scanner manufacturer."] + + + + } + + + @L["Scanner model"] + @L["Unknown (scanner model)"] + @if (!_unknownScanScannerModel) + { + + + + @L["Please enter a valid scanner model."] + + + + } + + + @L["Color space"] + @L["Unknown (color space)"] + @if (!_unknownScanColorSpace) + { + + } + + + @L["Creation date"] + @L["Unknown (creation date)"] + @if (!_unknownScanCreationDate) + { + + + + @L["Please enter a correct creation date."] + + + + } + + + @L["Exif version"] + @L["Unknown (exif version)"] + @if (!_unknownScanExifVersion) + { + + + + @L["Please enter a valid Exif version."] + + + + } + + + @L["Horizontal resolution"] + @L["Unknown (horizontal resolution)"] + @if (!_unknownScanHorizontalResolution) + { + + + + @L["Please enter a valid horizontal resolution."] + + + + } + + + @L["Resolution unit"] + @L["Unknown (resolution unit)"] + @if (!_unknownScanResolutionUnit) + { + + } + + + @L["Software used"] + @L["Unknown (software used)"] + @if (!_unknownScanSoftwareUsed) + { + + + + @L["Please enter a valid software used."] + + + + } + + + @L["Vertical resolution"] + @L["Unknown (vertical resolution)"] + @if (!_unknownScanVerticalResolution) + { + + + + @L["Please enter a valid vertical resolution."] + + + + } + + + @L["User comments"] + @L["Unknown or empty (user comments)"] + @if (!_unknownScanComments) + { + + + + @L["Please enter valid comments."] + + + + } + + + +
} \ No newline at end of file diff --git a/Marechai/Pages/Admin/Details/MagazineIssue.razor.cs b/Marechai/Pages/Admin/Details/MagazineIssue.razor.cs index 4e8b8003..40c49437 100644 --- a/Marechai/Pages/Admin/Details/MagazineIssue.razor.cs +++ b/Marechai/Pages/Admin/Details/MagazineIssue.razor.cs @@ -25,27 +25,68 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; +using System.Text.Json; using System.Threading.Tasks; using Blazorise; +using Marechai.Database; +using Marechai.Database.Models; +using Marechai.Helpers; using Marechai.Shared; using Marechai.ViewModels; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; +using Tewr.Blazor.FileReader; namespace Marechai.Pages.Admin.Details { public partial class MagazineIssue { - bool _addingMachine; - bool _addingMachineFamily; - int? _addingMachineFamilyId; - int? _addingMachineId; - + const int _maxUploadSize = 25 * 1048576; + bool _addingMachine; + bool _addingMachineFamily; + int? _addingMachineFamilyId; + int? _addingMachineId; bool _addingPerson; int? _addingPersonId; string _addingPersonRoleId; + bool _addingScan; + bool? _addToDatabase; + bool _allFinished; AuthenticationState _authState; + bool? _convertAvif1440; + bool? _convertAvif1440Th; + bool? _convertAvif4k; + bool? _convertAvif4kTh; + bool? _convertAvifHd; + bool? _convertAvifHdTh; + bool? _convertHeif1440; + bool? _convertHeif1440Th; + bool? _convertHeif4k; + bool? _convertHeif4kTh; + bool? _convertHeifHd; + bool? _convertHeifHdTh; + bool? _convertJp2k1440; + bool? _convertJp2k1440Th; + bool? _convertJp2k4k; + bool? _convertJp2k4kTh; + bool? _convertJp2kHd; + bool? _convertJp2kHdTh; + bool? _convertJpeg1440; + bool? _convertJpeg1440Th; + bool? _convertJpeg4k; + bool? _convertJpeg4kTh; + bool? _convertJpegHd; + bool? _convertJpegHdTh; + bool? _convertWebp1440; + bool? _convertWebp1440Th; + bool? _convertWebp4k; + bool? _convertWebp4kTh; + bool? _convertWebpHd; + bool? _convertWebpHdTh; bool _creating; MagazineByMachineViewModel _currentMagazineByMachine; MagazineByMachineFamilyViewModel _currentMagazineByMachineFamily; @@ -56,8 +97,13 @@ namespace Marechai.Pages.Admin.Details bool _deletingMagazineByMachine; bool _deletingMagazineByMachineFamily; bool _deletingPersonByMagazine; + bool _deletingScan; bool _editing; + bool _editingScan; + bool? _extractExif; Modal _frmDelete; + string _imageFormat; + ElementReference _inputUpload; bool _loaded; List _machineFamilies; List _machines; @@ -66,20 +112,79 @@ namespace Marechai.Pages.Admin.Details List _magazinePeople; List _magazines; MagazineIssueViewModel _model; + bool? _moveFile; List _people; + double _progressValue; List _roles; bool _savingMachine; bool _savingMachineFamily; bool _savingPerson; + List _scans; + ApplicationUser _scanUser; + MagazineScanViewModel _selectedScan; bool _unknownIssueNumber; bool _unknownNativeCaption; bool _unknownPages; bool _unknownProductCode; bool _unknownPublished; + bool _unknownScanAuthor; + bool _unknownScanColorSpace; + bool _unknownScanComments; + bool _unknownScanCreationDate; + bool _unknownScanExifVersion; + bool _unknownScanHorizontalResolution; + bool _unknownScanPage; + bool _unknownScanResolutionUnit; + bool _unknownScanScannerManufacturer; + bool _unknownScanScannerModel; + bool _unknownScanSoftwareUsed; + bool _unknownScanVerticalResolution; + bool _uploaded; + bool _uploadError; + string _uploadErrorMessage; + bool _uploading; + uint? _uploadScanPage; + uint _uploadScanType; [Parameter] public long Id { get; set; } + ushort ScanColorSpace + { + get + { + if(_selectedScan.ColorSpace is null) + return 0; + + return (ushort)_selectedScan.ColorSpace; + } + set => _selectedScan.ColorSpace = (ColorSpace)value; + } + + ushort ScanResolutionUnit + { + get + { + if(_selectedScan.ResolutionUnit is null) + return 0; + + return (ushort)_selectedScan.ResolutionUnit; + } + set => _selectedScan.ResolutionUnit = (ResolutionUnit)value; + } + + uint ScanType + { + get + { + if(_selectedScan.ResolutionUnit is null) + return 0; + + return (uint)_selectedScan.ResolutionUnit; + } + set => _selectedScan.Type = (DocumentScanType)value; + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if(_loaded) @@ -106,6 +211,7 @@ namespace Marechai.Pages.Admin.Details _magazineMachines = await MagazinesByMachineService.GetByMagazine(Id); _addingPersonRoleId = _roles.First().Id; _magazinePeople = await PeopleByMagazineService.GetByMagazine(Id); + _scans = await MagazineScansService.GetGuidsByMagazineAsync(Id); _editing = _creating || NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLowerInvariant(). StartsWith("admin/magazine_issues/edit/", @@ -216,6 +322,8 @@ namespace Marechai.Pages.Admin.Details _currentMagazineByMachine = null; _deletingPersonByMagazine = false; _currentPersonByMagazine = null; + _deletingScan = false; + _selectedScan = null; } void HideModal() => _frmDelete.Hide(); @@ -228,6 +336,8 @@ namespace Marechai.Pages.Admin.Details await ConfirmDeleteMagazineByMachine(); else if(_deletingPersonByMagazine) await ConfirmDeletePersonByMagazine(); + else if(_deletingScan) + await ConfirmDeleteScan(); } void OnAddFamilyClick() @@ -477,5 +587,760 @@ namespace Marechai.Pages.Admin.Details // Tell we finished loading StateHasChanged(); } + + void ValidateScanAuthor(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Scan author must be smaller than 256 characters."], 256); + + void ValidateScannerManufacturer(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Scanner manufacturer must be smaller than 256 characters."], 256); + + void ValidateScannerModel(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Scanner model must be smaller than 256 characters."], 256); + + void ValidateDate(ValidatorEventArgs e) + { + if(!(e.Value is DateTime item) || + item.Year <= 1816 || + item >= DateTime.UtcNow) + e.Status = ValidationStatus.Error; + else + e.Status = ValidationStatus.Success; + } + + void ValidateDoubleBiggerThanZero(ValidatorEventArgs e) + { + if(e.Value is double item && + item > 0) + e.Status = ValidationStatus.Success; + else + e.Status = ValidationStatus.Error; + } + + void ValidateExifVersion(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Exif version must be 255 characters or less."], 255); + + void ValidateLens(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Lens name must be 255 characters or less."], 255); + + void ValidateSoftwareUsed(ValidatorEventArgs e) => + Validators.ValidateString(e, L["Software used must be 255 characters or less."], 255); + + void ValidateComments(ValidatorEventArgs e) => + Validators.ValidateString(e, L["User comments must be 255 characters or less."], 255); + + void ValidateUnsignedIntegerBiggerThanZero(ValidatorEventArgs e) + { + if(e.Value is uint item && + item > 0) + e.Status = ValidationStatus.Success; + else + e.Status = ValidationStatus.Error; + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + async Task UploadFile() + { + var processExiftool = new Process + { + StartInfo = + { + FileName = "exiftool", + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true + } + }; + + var processIdentify = new Process + { + StartInfo = + { + FileName = "identify", + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true + } + }; + + var processConvert = new Process + { + StartInfo = + { + FileName = "convert", + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true + } + }; + + string identifyOutput; + string convertOutput; + string exiftoolOutput; + + try + { + processIdentify.Start(); + identifyOutput = await processIdentify.StandardOutput.ReadToEndAsync(); + processIdentify.WaitForExit(); + processConvert.Start(); + convertOutput = await processConvert.StandardOutput.ReadToEndAsync(); + processConvert.WaitForExit(); + processExiftool.Start(); + exiftoolOutput = await processExiftool.StandardOutput.ReadToEndAsync(); + processExiftool.WaitForExit(); + } + catch(Exception) + { + _uploadError = true; + _uploadErrorMessage = L["Cannot run ImageMagick please contact the administrator."]; + + return; + } + + IFileReference file = (await FileReaderService.CreateReference(_inputUpload).EnumerateFilesAsync()). + FirstOrDefault(); + + if(file is null) + return; + + IFileInfo fileInfo = await file.ReadFileInfoAsync(); + + if(fileInfo.Size > _maxUploadSize) + { + _uploadError = true; + _uploadErrorMessage = L["The selected file is too big."]; + + return; + } + + string tmpPath = Path.GetTempFileName(); + + FileStream outFs; + + try + { + outFs = new FileStream(tmpPath, FileMode.Open, FileAccess.ReadWrite); + } + catch(Exception) + { + _uploadError = true; + _uploadErrorMessage = L["There was an error uploading the file."]; + + return; + } + + _uploading = true; + + await using AsyncDisposableStream fs = await file.OpenReadAsync(); + byte[] buffer = new byte[20480]; + + try + { + double lastProgress = 0; + int count; + + while((count = await fs.ReadAsync(buffer, 0, buffer.Length)) != 0) + { + await outFs.WriteAsync(buffer, 0, count); + + double progress = ((double)fs.Position * 100) / fs.Length; + + if(!(progress > lastProgress + 0.01)) + continue; + + _progressValue = progress; + await InvokeAsync(StateHasChanged); + await Task.Yield(); + lastProgress = progress; + } + } + catch(Exception) + { + _uploading = false; + _uploadError = true; + _uploadErrorMessage = L["There was an error uploading the file."]; + + return; + } + + outFs.Close(); + _uploading = false; + await InvokeAsync(StateHasChanged); + await Task.Yield(); + + processIdentify = new Process + { + StartInfo = + { + FileName = "identify", + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + ArgumentList = + { + tmpPath + } + } + }; + + processIdentify.Start(); + identifyOutput = await processIdentify.StandardOutput.ReadToEndAsync(); + processIdentify.WaitForExit(); + + if(processIdentify.ExitCode != 0 || + string.IsNullOrWhiteSpace(identifyOutput)) + { + _uploading = false; + _uploadError = true; + _uploadErrorMessage = L["The uploaded file was not recognized as an image."]; + File.Delete(tmpPath); + + return; + } + + string[] pieces = identifyOutput.Substring(tmpPath.Length). + Split(" ", StringSplitOptions.RemoveEmptyEntries); + + if(pieces.Length < 2) + { + _uploading = false; + _uploadError = true; + _uploadErrorMessage = L["The uploaded file was not recognized as an image."]; + File.Delete(tmpPath); + + return; + } + + // TODO: Move this to Helpers, keep progress + + string extension = ImageMagick.GetExtension(pieces[0]); + + if(string.IsNullOrWhiteSpace(extension)) + { + _uploading = false; + _uploadError = true; + _uploadErrorMessage = L["The uploaded file was not recognized as an image."]; + File.Delete(tmpPath); + + return; + } + + _imageFormat = pieces[0]; + _uploaded = true; + + _selectedScan = new MagazineScanViewModel + { + UserId = (await UserManager.GetUserAsync(_authState.User)).Id, + MagazineId = Id, + Id = Guid.NewGuid(), + OriginalExtension = extension, + UploadDate = DateTime.UtcNow, + Page = _unknownScanPage ? null : _uploadScanPage, + Type = (DocumentScanType)_uploadScanType + }; + + try + { + processExiftool = new Process + { + StartInfo = + { + FileName = "exiftool", + CreateNoWindow = true, + RedirectStandardError = true, + RedirectStandardOutput = true, + ArgumentList = + { + "-n", + "-json", + tmpPath + } + } + }; + + processExiftool.Start(); + exiftoolOutput = await processExiftool.StandardOutput.ReadToEndAsync(); + processExiftool.WaitForExit(); + + Exif[] exif = JsonSerializer.Deserialize(exiftoolOutput); + + if(exif?.Length >= 1) + exif[0].ToViewModel(_selectedScan); + + _extractExif = true; + } + catch(Exception) + { + _extractExif = false; + } + + string originalFilePath = Path.Combine(Host.WebRootPath, "assets", "scans", "magazines", "originals", + $"{_selectedScan.Id}{_selectedScan.OriginalExtension}"); + + try + { + File.Move(tmpPath, originalFilePath); + _moveFile = true; + } + catch(Exception) + { + _moveFile = false; + File.Delete(tmpPath); + + return; + } + + await Task.Yield(); + await InvokeAsync(StateHasChanged); + + var photos = new Photos(); + photos.FinishedAll += OnFinishedAll; + photos.FinishedRenderingJpeg4k += OnFinishedRenderingJpeg4k; + photos.FinishedRenderingJpeg1440 += OnFinishedRenderingJpeg1440; + photos.FinishedRenderingJpegHd += OnFinishedRenderingJpegHd; + photos.FinishedRenderingJpeg4kThumbnail += OnFinishedRenderingJpeg4kThumbnail; + photos.FinishedRenderingJpeg1440Thumbnail += OnFinishedRenderingJpeg1440Thumbnail; + photos.FinishedRenderingJpegHdThumbnail += OnFinishedRenderingJpegHdThumbnail; + photos.FinishedRenderingJp2k4k += OnFinishedRenderingJp2k4k; + photos.FinishedRenderingJp2k1440 += OnFinishedRenderingJp2k1440; + photos.FinishedRenderingJp2kHd += OnFinishedRenderingJp2kHd; + photos.FinishedRenderingJp2k4kThumbnail += OnFinishedRenderingJp2k4kThumbnail; + photos.FinishedRenderingJp2k1440Thumbnail += OnFinishedRenderingJp2k1440Thumbnail; + photos.FinishedRenderingJp2kHdThumbnail += OnFinishedRenderingJp2kHdThumbnail; + photos.FinishedRenderingWebp4k += OnFinishedRenderingWebp4k; + photos.FinishedRenderingWebp1440 += OnFinishedRenderingWebp1440; + photos.FinishedRenderingWebpHd += OnFinishedRenderingWebpHd; + photos.FinishedRenderingWebp4kThumbnail += OnFinishedRenderingWebp4kThumbnail; + photos.FinishedRenderingWebp1440Thumbnail += OnFinishedRenderingWebp1440Thumbnail; + photos.FinishedRenderingWebpHdThumbnail += OnFinishedRenderingWebpHdThumbnail; + photos.FinishedRenderingHeif4k += OnFinishedRenderingHeif4k; + photos.FinishedRenderingHeif1440 += OnFinishedRenderingHeif1440; + photos.FinishedRenderingHeifHd += OnFinishedRenderingHeifHd; + photos.FinishedRenderingHeif4kThumbnail += OnFinishedRenderingHeif4kThumbnail; + photos.FinishedRenderingHeif1440Thumbnail += OnFinishedRenderingHeif1440Thumbnail; + photos.FinishedRenderingHeifHdThumbnail += OnFinishedRenderingHeifHdThumbnail; + photos.FinishedRenderingAvif4k += OnFinishedRenderingAvif4k; + photos.FinishedRenderingAvif1440 += OnFinishedRenderingAvif1440; + photos.FinishedRenderingAvifHd += OnFinishedRenderingAvifHd; + photos.FinishedRenderingAvif4kThumbnail += OnFinishedRenderingAvif4kThumbnail; + photos.FinishedRenderingAvif1440Thumbnail += OnFinishedRenderingAvif1440Thumbnail; + photos.FinishedRenderingAvifHdThumbnail += OnFinishedRenderingAvifHdThumbnail; + + #pragma warning disable 4014 + Task.Run(() => photos.ConversionWorker(Host.WebRootPath, _selectedScan.Id, originalFilePath, _imageFormat, + true, "magazines")); + #pragma warning restore 4014 + } + + async Task OnFinishedRenderingJpeg4k(bool result) + { + _convertJpeg4k = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJpeg1440(bool result) + { + _convertJpeg1440 = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJpegHd(bool result) + { + _convertJpegHd = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJpeg4kThumbnail(bool result) + { + _convertJpeg4kTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJpeg1440Thumbnail(bool result) + { + _convertJpeg1440Th = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJpegHdThumbnail(bool result) + { + _convertJpegHdTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2k4k(bool result) + { + _convertJp2k4k = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2k1440(bool result) + { + _convertJp2k1440 = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2kHd(bool result) + { + _convertJp2kHd = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2k4kThumbnail(bool result) + { + _convertJp2k4kTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2k1440Thumbnail(bool result) + { + _convertJp2k1440Th = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingJp2kHdThumbnail(bool result) + { + _convertJp2kHdTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebp4k(bool result) + { + _convertWebp4k = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebp1440(bool result) + { + _convertWebp1440 = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebpHd(bool result) + { + _convertWebpHd = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebp4kThumbnail(bool result) + { + _convertWebp4kTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebp1440Thumbnail(bool result) + { + _convertWebp1440Th = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingWebpHdThumbnail(bool result) + { + _convertWebpHdTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeif4k(bool result) + { + _convertHeif4k = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeif1440(bool result) + { + _convertHeif1440 = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeifHd(bool result) + { + _convertHeifHd = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeif4kThumbnail(bool result) + { + _convertHeif4kTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeif1440Thumbnail(bool result) + { + _convertHeif1440Th = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingHeifHdThumbnail(bool result) + { + _convertHeifHdTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvif4k(bool result) + { + _convertAvif4k = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvif1440(bool result) + { + _convertAvif1440 = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvifHd(bool result) + { + _convertAvifHd = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvif4kThumbnail(bool result) + { + _convertAvif4kTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvif1440Thumbnail(bool result) + { + _convertAvif1440Th = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedRenderingAvifHdThumbnail(bool result) + { + _convertAvifHdTh = result; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task OnFinishedAll(bool result) + { + try + { + await MagazineScansService.CreateAsync(_selectedScan, + (await UserManager.GetUserAsync(_authState.User)).Id); + + _addToDatabase = true; + } + catch(Exception e) + { + _addToDatabase = false; + Console.WriteLine(e); + + throw; + } + + _allFinished = true; + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task AddScan() + { + _editing = false; + _editingScan = false; + _addingScan = true; + + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task EditScan(Guid scanId) + { + _selectedScan = await MagazineScansService.GetAsync(scanId); + + if(_selectedScan is null) + return; + + _scanUser = await UserManager.FindByIdAsync(_selectedScan.UserId); + _unknownScanAuthor = string.IsNullOrWhiteSpace(_selectedScan.Author); + _unknownScanScannerManufacturer = string.IsNullOrWhiteSpace(_selectedScan.ScannerModel); + _unknownScanScannerModel = string.IsNullOrWhiteSpace(_selectedScan.ScannerModel); + _unknownScanColorSpace = !_selectedScan.ColorSpace.HasValue; + _unknownScanCreationDate = !_selectedScan.CreationDate.HasValue; + _unknownScanExifVersion = string.IsNullOrWhiteSpace(_selectedScan.ExifVersion); + _unknownScanHorizontalResolution = !_selectedScan.HorizontalResolution.HasValue; + _unknownScanResolutionUnit = !_selectedScan.ResolutionUnit.HasValue; + _unknownScanSoftwareUsed = string.IsNullOrWhiteSpace(_selectedScan.SoftwareUsed); + _unknownScanVerticalResolution = !_selectedScan.VerticalResolution.HasValue; + _unknownScanComments = string.IsNullOrWhiteSpace(_selectedScan.Comments); + _unknownScanPage = !_selectedScan.Page.HasValue; + + _editing = false; + _editingScan = true; + _addingScan = false; + + await Task.Yield(); + await InvokeAsync(StateHasChanged); + } + + async Task ShowScanDeleteModal(Guid scanId) + { + _selectedScan = await MagazineScansService.GetAsync(scanId); + + if(_selectedScan is null) + return; + + _deletingScan = true; + _deleteTitle = L["Delete scan from this magazine"]; + + _deleteText = _selectedScan.Page.HasValue + ? string. + Format(L["Are you sure you want to delete the scan type {0} for page {1} from this magazine?"], + _selectedScan.Type, _selectedScan.Page) + : string. + Format(L["Are you sure you want to delete the scan type {0} from this magazine?"], + _selectedScan.Type); + + _frmDelete.Show(); + } + + async Task ConfirmDeleteScan() + { + if(_selectedScan is null) + return; + + _deleteInProgress = true; + + // Yield thread to let UI to update + await Task.Yield(); + + // TODO: Delete files + await MagazineScansService.DeleteAsync(_selectedScan.Id, + (await UserManager.GetUserAsync(_authState.User)).Id); + + _scans = await MagazineScansService.GetGuidsByMagazineAsync(Id); + + _deleteInProgress = false; + _frmDelete.Hide(); + + // Yield thread to let UI to update + await Task.Yield(); + + // Tell we finished loading + StateHasChanged(); + } + + void OnCancelScanClicked() + { + _scanUser = null; + _unknownScanAuthor = true; + _unknownScanScannerManufacturer = true; + _unknownScanScannerModel = true; + _unknownScanColorSpace = true; + _unknownScanCreationDate = true; + _unknownScanExifVersion = true; + _unknownScanHorizontalResolution = true; + _unknownScanResolutionUnit = true; + _unknownScanSoftwareUsed = true; + _unknownScanVerticalResolution = true; + _unknownScanComments = true; + _unknownScanPage = true; + _selectedScan = null; + + _editing = false; + _editingScan = false; + _addingScan = false; + + StateHasChanged(); + } + + async void OnSaveScanClicked() + { + if(_unknownScanAuthor) + _selectedScan.Author = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.Author)) + return; + + if(_unknownScanScannerManufacturer) + _selectedScan.ScannerModel = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.ScannerModel)) + return; + + if(_unknownScanScannerModel) + _selectedScan.ScannerManufacturer = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.ScannerManufacturer)) + return; + + if(_unknownScanColorSpace) + _selectedScan.ColorSpace = null; + else if(!_selectedScan.ColorSpace.HasValue) + return; + + if(_unknownScanCreationDate) + _selectedScan.CreationDate = null; + else if(_selectedScan.CreationDate > DateTime.UtcNow) + return; + + if(_unknownScanExifVersion) + _selectedScan.ExifVersion = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.ExifVersion)) + return; + + if(_unknownScanHorizontalResolution) + _selectedScan.HorizontalResolution = null; + else if(!_selectedScan.HorizontalResolution.HasValue) + return; + + if(_unknownScanResolutionUnit) + _selectedScan.ResolutionUnit = null; + else if(!_selectedScan.ResolutionUnit.HasValue) + return; + + if(_unknownScanSoftwareUsed) + _selectedScan.SoftwareUsed = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.SoftwareUsed)) + return; + + if(_unknownScanVerticalResolution) + _selectedScan.VerticalResolution = null; + else if(!_selectedScan.VerticalResolution.HasValue) + return; + + if(_unknownScanComments) + _selectedScan.Comments = null; + else if(string.IsNullOrWhiteSpace(_selectedScan.Comments)) + return; + + if(_unknownScanPage) + _selectedScan.Page = null; + else if(!_selectedScan.Page.HasValue || + _selectedScan.Page == 0) + return; + + await MagazineScansService.UpdateAsync(_selectedScan, (await UserManager.GetUserAsync(_authState.User)).Id); + + OnCancelScanClicked(); + StateHasChanged(); + } } } \ No newline at end of file diff --git a/Marechai/Services/MagazineScansService.cs b/Marechai/Services/MagazineScansService.cs new file mode 100644 index 00000000..a76f71d9 --- /dev/null +++ b/Marechai/Services/MagazineScansService.cs @@ -0,0 +1,134 @@ +/****************************************************************************** +// MARECHAI: Master repository of computing history artifacts information +// ---------------------------------------------------------------------------- +// +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2003-2020 Natalia Portillo +*******************************************************************************/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Marechai.Database.Models; +using Marechai.ViewModels; +using Microsoft.EntityFrameworkCore; + +namespace Marechai.Services +{ + public class MagazineScansService + { + readonly MarechaiContext _context; + + public MagazineScansService(MarechaiContext context) => _context = context; + + public async Task> GetGuidsByMagazineAsync(long bookId) => + await _context.MagazineScans.Where(p => p.MagazineId == bookId).Select(p => p.Id).ToListAsync(); + + public async Task GetAsync(Guid id) => + await _context.MagazineScans.Where(p => p.Id == id).Select(p => new MagazineScanViewModel + { + Author = p.Author, + MagazineId = p.Magazine.Id, + ColorSpace = p.ColorSpace, + Comments = p.Comments, + CreationDate = p.CreationDate, + ExifVersion = p.ExifVersion, + HorizontalResolution = p.HorizontalResolution, + Id = p.Id, + ResolutionUnit = p.ResolutionUnit, + Page = p.Page, + ScannerManufacturer = p.ScannerManufacturer, + ScannerModel = p.ScannerModel, + SoftwareUsed = p.SoftwareUsed, + Type = p.Type, + UploadDate = p.UploadDate, + UserId = p.UserId, + VerticalResolution = p.VerticalResolution, + OriginalExtension = p.OriginalExtension + }).FirstOrDefaultAsync(); + + public async Task UpdateAsync(MagazineScanViewModel viewModel, string userId) + { + MagazineScan model = await _context.MagazineScans.FindAsync(viewModel.Id); + + if(model is null) + return; + + model.Author = viewModel.Author; + model.ColorSpace = viewModel.ColorSpace; + model.Comments = viewModel.Comments; + model.CreationDate = viewModel.CreationDate; + model.ExifVersion = viewModel.ExifVersion; + model.HorizontalResolution = viewModel.HorizontalResolution; + model.ResolutionUnit = viewModel.ResolutionUnit; + model.Page = viewModel.Page; + model.ScannerManufacturer = viewModel.ScannerManufacturer; + model.ScannerModel = viewModel.ScannerModel; + model.Type = viewModel.Type; + model.SoftwareUsed = viewModel.SoftwareUsed; + model.VerticalResolution = viewModel.VerticalResolution; + + await _context.SaveChangesWithUserAsync(userId); + } + + public async Task CreateAsync(MagazineScanViewModel viewModel, string userId) + { + var model = new MagazineScan + { + Author = viewModel.Author, + MagazineId = viewModel.MagazineId, + ColorSpace = viewModel.ColorSpace, + Comments = viewModel.Comments, + CreationDate = viewModel.CreationDate, + ExifVersion = viewModel.ExifVersion, + HorizontalResolution = viewModel.HorizontalResolution, + Id = viewModel.Id, + ResolutionUnit = viewModel.ResolutionUnit, + Page = viewModel.Page, + ScannerManufacturer = viewModel.ScannerManufacturer, + ScannerModel = viewModel.ScannerModel, + Type = viewModel.Type, + SoftwareUsed = viewModel.SoftwareUsed, + UploadDate = viewModel.UploadDate, + UserId = viewModel.UserId, + VerticalResolution = viewModel.VerticalResolution, + OriginalExtension = viewModel.OriginalExtension + }; + + await _context.MagazineScans.AddAsync(model); + await _context.SaveChangesWithUserAsync(userId); + + return model.Id; + } + + public async Task DeleteAsync(Guid id, string userId) + { + MagazineScan item = await _context.MagazineScans.FindAsync(id); + + if(item is null) + return; + + _context.MagazineScans.Remove(item); + + await _context.SaveChangesWithUserAsync(userId); + } + } +} \ No newline at end of file diff --git a/Marechai/Services/Register.cs b/Marechai/Services/Register.cs index d5eb52da..24f97133 100644 --- a/Marechai/Services/Register.cs +++ b/Marechai/Services/Register.cs @@ -96,6 +96,7 @@ namespace Marechai.Services services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } } \ No newline at end of file diff --git a/Marechai/ViewModels/MagazineScanViewModel.cs b/Marechai/ViewModels/MagazineScanViewModel.cs new file mode 100644 index 00000000..02e07bd5 --- /dev/null +++ b/Marechai/ViewModels/MagazineScanViewModel.cs @@ -0,0 +1,33 @@ +/****************************************************************************** +// MARECHAI: Master repository of computing history artifacts information +// ---------------------------------------------------------------------------- +// +// Author(s) : Natalia Portillo +// +// --[ License ] -------------------------------------------------------------- +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// ---------------------------------------------------------------------------- +// Copyright © 2003-2020 Natalia Portillo +*******************************************************************************/ + +namespace Marechai.ViewModels +{ + public class MagazineScanViewModel : DocumentScanBaseViewModel + { + public long MagazineId { get; set; } + public string Magazine { get; set; } + } +} \ No newline at end of file