Fixed a bug in ScanLibrary that caused duplicated Volumes. Implemented APIs for navigating down to Volume for webui.

This is rough code and needs to be polished and refactored.
This commit is contained in:
Joseph Milazzo 2021-01-01 14:04:31 -06:00
parent 380c3e7b3c
commit c429c50ba2
18 changed files with 709 additions and 58 deletions

View File

@ -21,10 +21,11 @@ namespace API.Controllers
private readonly IUserRepository _userRepository;
private readonly IMapper _mapper;
private readonly ITaskScheduler _taskScheduler;
private readonly ISeriesRepository _seriesRepository;
public LibraryController(IDirectoryService directoryService,
ILibraryRepository libraryRepository, ILogger<LibraryController> logger, IUserRepository userRepository,
IMapper mapper, ITaskScheduler taskScheduler)
IMapper mapper, ITaskScheduler taskScheduler, ISeriesRepository seriesRepository)
{
_directoryService = directoryService;
_libraryRepository = libraryRepository;
@ -32,6 +33,7 @@ namespace API.Controllers
_userRepository = userRepository;
_mapper = mapper;
_taskScheduler = taskScheduler;
_seriesRepository = seriesRepository;
}
/// <summary>
@ -79,7 +81,7 @@ namespace API.Controllers
return Ok(user);
}
return BadRequest("Not Implemented");
return BadRequest("There was a critical issue. Please try again.");
}
[Authorize(Policy = "RequireAdminRole")]
@ -91,8 +93,20 @@ namespace API.Controllers
// We have to send a json encoded Library (aka a DTO) to the Background Job thread.
// Because we use EF, we have circular dependencies back to Library and it will crap out
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(library));
return Ok();
}
[HttpGet("libraries-for")]
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser(string username)
{
return Ok(await _libraryRepository.GetLibrariesForUsernameAysnc(username));
}
[HttpGet("series")]
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId)
{
return Ok(await _seriesRepository.GetSeriesForLibraryIdAsync(libraryId));
}
}
}

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using API.DTOs;
using API.Interfaces;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace API.Controllers
{
public class SeriesController : BaseApiController
{
private readonly ILogger<SeriesController> _logger;
private readonly IMapper _mapper;
private readonly ITaskScheduler _taskScheduler;
private readonly ISeriesRepository _seriesRepository;
public SeriesController(ILogger<SeriesController> logger, IMapper mapper,
ITaskScheduler taskScheduler, ISeriesRepository seriesRepository)
{
_logger = logger;
_mapper = mapper;
_taskScheduler = taskScheduler;
_seriesRepository = seriesRepository;
}
[HttpGet("{seriesId}")]
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
{
return Ok(await _seriesRepository.GetSeriesByIdAsync(seriesId));
}
[HttpGet("volumes")]
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
{
return Ok(await _seriesRepository.GetVolumesAsync(seriesId));
}
}
}

View File

@ -23,6 +23,7 @@ namespace API.Controllers
_libraryRepository = libraryRepository;
}
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("add-library")]
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
{

View File

@ -5,6 +5,7 @@ namespace API.DTOs
{
public class LibraryDto
{
public int Id { get; init; }
public string Name { get; set; }
public string CoverImage { get; set; }
public LibraryType Type { get; set; }

13
API/DTOs/SeriesDto.cs Normal file
View File

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace API.DTOs
{
public class SeriesDto
{
public int Id { get; set; }
public string Name { get; set; }
public string OriginalName { get; set; }
public string SortName { get; set; }
public string Summary { get; set; }
}
}

12
API/DTOs/VolumeDto.cs Normal file
View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace API.DTOs
{
public class VolumeDto
{
public int Id { get; set; }
public string Number { get; set; }
public string CoverImage { get; set; }
public ICollection<string> Files { get; set; }
}
}

View File

@ -6,6 +6,7 @@ using API.Entities;
using API.Interfaces;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace API.Data
@ -45,6 +46,14 @@ namespace API.Data
.Single();
}
public async Task<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName)
{
return await _context.Library
.Include(l => l.AppUsers)
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
}
public async Task<IEnumerable<LibraryDto>> GetLibrariesAsync()
{
return await _context.Library

View File

@ -0,0 +1,491 @@
// <auto-generated />
using System;
using API.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace API.Data.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20210101180935_AddedCoverImageToSeries")]
partial class AddedCoverImageToSeries
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.1");
modelBuilder.Entity("API.Entities.AppRole", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("API.Entities.AppUser", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastActive")
.HasColumnType("TEXT");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("API.Entities.AppUserRole", b =>
{
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("FolderPath");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Library");
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FilePath")
.HasColumnType("TEXT");
b.Property<int>("VolumeId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("VolumeId");
b.ToTable("MangaFile");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("OriginalName")
.HasColumnType("TEXT");
b.Property<string>("SortName")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("Series");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Number")
.HasColumnType("TEXT");
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SeriesId");
b.ToTable("Volume");
});
modelBuilder.Entity("AppUserLibrary", b =>
{
b.Property<int>("AppUsersId")
.HasColumnType("INTEGER");
b.Property<int>("LibrariesId")
.HasColumnType("INTEGER");
b.HasKey("AppUsersId", "LibrariesId");
b.HasIndex("LibrariesId");
b.ToTable("AppUserLibrary");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("API.Entities.AppUserRole", b =>
{
b.HasOne("API.Entities.AppRole", "Role")
.WithMany("UserRoles")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Entities.AppUser", "User")
.WithMany("UserRoles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Role");
b.Navigation("User");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.HasOne("API.Entities.Library", "Library")
.WithMany("Folders")
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Library");
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
{
b.HasOne("API.Entities.Volume", "Volume")
.WithMany("Files")
.HasForeignKey("VolumeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Volume");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.HasOne("API.Entities.Library", "Library")
.WithMany("Series")
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Library");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.HasOne("API.Entities.Series", "Series")
.WithMany("Volumes")
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Series");
});
modelBuilder.Entity("AppUserLibrary", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("AppUsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("API.Entities.Library", null)
.WithMany()
.HasForeignKey("LibrariesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.HasOne("API.Entities.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("API.Entities.AppRole", b =>
{
b.Navigation("UserRoles");
});
modelBuilder.Entity("API.Entities.AppUser", b =>
{
b.Navigation("UserRoles");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Navigation("Folders");
b.Navigation("Series");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Navigation("Volumes");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Navigation("Files");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace API.Data.Migrations
{
public partial class AddedCoverImageToSeries : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "CoverImage",
table: "Series",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CoverImage",
table: "Series");
}
}
}

View File

@ -200,6 +200,9 @@ namespace API.Data.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");

View File

@ -1,7 +1,11 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Interfaces;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data
@ -9,10 +13,12 @@ namespace API.Data
public class SeriesRepository : ISeriesRepository
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public SeriesRepository(DataContext context)
public SeriesRepository(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public void Update(Series series)
@ -39,5 +45,39 @@ namespace API.Data
{
return _context.Series.SingleOrDefault(x => x.Name == name);
}
public async Task<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId)
{
return await _context.Series
.Where(series => series.LibraryId == libraryId)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).ToListAsync();
}
public async Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId)
{
return await _context.Volume
.Where(vol => vol.SeriesId == seriesId)
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToListAsync();
}
public IEnumerable<VolumeDto> GetVolumesDto(int seriesId)
{
return _context.Volume
.Where(vol => vol.SeriesId == seriesId)
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToList();
}
public IEnumerable<Volume> GetVolumes(int seriesId)
{
return _context.Volume
.Where(vol => vol.SeriesId == seriesId)
.ToList();
}
public async Task<SeriesDto> GetSeriesByIdAsync(int seriesId)
{
return await _context.Series.Where(x => x.Id == seriesId)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync();
}
}
}

View File

@ -4,8 +4,7 @@
{
public int Id { get; set; }
public string FilePath { get; set; }
//public string FileExtension { get; set; }
// Relationship Mapping
public Volume Volume { get; set; }
public int VolumeId { get; set; }

View File

@ -21,6 +21,7 @@ namespace API.Entities
/// Summary information related to the Series
/// </summary>
public string Summary { get; set; }
public string CoverImage { get; set; }
public ICollection<Volume> Volumes { get; set; }
public Library Library { get; set; }

View File

@ -7,7 +7,7 @@ namespace API.Entities
public int Id { get; set; }
public string Number { get; set; }
public ICollection<MangaFile> Files { get; set; }
// Many-to-Many relationships
public Series Series { get; set; }
public int SeriesId { get; set; }

View File

@ -10,6 +10,12 @@ namespace API.Helpers
public AutoMapperProfiles()
{
CreateMap<LibraryDto, Library>();
CreateMap<Volume, VolumeDto>()
.ForMember(dest => dest.Files,
opt => opt.MapFrom(src => src.Files.Select(x => x.FilePath).ToList()));
CreateMap<Series, SeriesDto>();
CreateMap<Library, LibraryDto>()
.ForMember(dest => dest.Folders,

View File

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using Microsoft.AspNetCore.Mvc;
namespace API.Interfaces
{
@ -20,5 +21,7 @@ namespace API.Interfaces
Task<Library> GetLibraryForIdAsync(int libraryId);
bool SaveAll();
Library GetLibraryForName(string libraryName);
Task<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName);
//Task<IEnumerable<Series>> GetSeriesForIdAsync(int libraryId);
}
}

View File

@ -1,4 +1,6 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
namespace API.Interfaces
@ -10,5 +12,11 @@ namespace API.Interfaces
Task<Series> GetSeriesByNameAsync(string name);
Series GetSeriesByName(string name);
bool SaveAll();
Task<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId);
Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId);
IEnumerable<VolumeDto> GetVolumesDto(int seriesId);
IEnumerable<Volume> GetVolumes(int seriesId);
Task<SeriesDto> GetSeriesByIdAsync(int seriesId);
}
}

View File

@ -135,6 +135,7 @@ namespace API.Services
private Series UpdateSeries(string seriesName, ParserInfo[] infos)
{
var series = _seriesRepository.GetSeriesByName(seriesName);
ICollection<Volume> volumes = new List<Volume>();;
if (series == null)
{
@ -146,24 +147,45 @@ namespace API.Services
Summary = "",
};
}
ICollection<Volume> volumes = new List<Volume>();
// BUG: This is creating new volume entries and not resetting each run.
IEnumerable<Volume> existingVolumes = _seriesRepository.GetVolumes(series.Id);
foreach (var info in infos)
{
volumes.Add(new Volume()
var existingVolume = existingVolumes.SingleOrDefault(v => v.Number == info.Volumes);
if (existingVolume != null)
{
Number = info.Volumes,
Files = new List<MangaFile>() {new MangaFile()
// Temp let's overwrite all files (we need to enhance to update files)
existingVolume.Files = new List<MangaFile>()
{
FilePath = info.File
}}
});
new MangaFile()
{
FilePath = info.File
}
};
volumes.Add(existingVolume);
}
else
{
var vol = new Volume()
{
Number = info.Volumes,
Files = new List<MangaFile>()
{
new MangaFile()
{
FilePath = info.File
}
}
};
volumes.Add(vol);
}
Console.WriteLine($"Adding volume {volumes.Last().Number} with File: {info.File}");
}
series.Volumes = volumes;
//_seriesRepository.Update(series);
return series;
}
@ -208,48 +230,14 @@ namespace API.Services
}
_libraryRepository.Update(libraryEntity);
// This is throwing a DbUpdateConcurrencyException due to multiple threads modifying Library at one time.
try
if (_libraryRepository.SaveAll())
{
if (_libraryRepository.SaveAll())
{
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
}
else
{
_logger.LogError("There was a critical error that resulted in a failed scan. Please rescan.");
}
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
}
catch (DbUpdateConcurrencyException ex)
else
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Series)
{
var proposedValues = entry.CurrentValues;
var databaseValues = entry.GetDatabaseValues();
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
Console.WriteLine($"Proposed ({proposedValue}) vs Database ({databaseValue})");
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues);
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
_logger.LogError("There was a critical error that resulted in a failed scan. Please rescan.");
}