diff --git a/songrequests_blazor/Components/Pages/Settings/Libraries.razor b/songrequests_blazor/Components/Pages/Settings/Libraries.razor index 5fd89d5..c609933 100644 --- a/songrequests_blazor/Components/Pages/Settings/Libraries.razor +++ b/songrequests_blazor/Components/Pages/Settings/Libraries.razor @@ -1,7 +1,9 @@ @page "/options/libraries" @using Microsoft.EntityFrameworkCore @using songrequests_blazor.Data +@using songrequests_blazor.Services @inject Microsoft.EntityFrameworkCore.IDbContextFactory DataContextFactory +

Bibliotheken

Bibliothek anlegen @@ -12,6 +14,7 @@
  • @itm.Name
    + Bearbeiten
    @@ -61,6 +64,12 @@ await modal.CloseAsync(); } + private void RunScan(Library itm) + { + ScanService.Queue.Add(itm); + } + + private async Task RemoveItem() { if (ModalItem == null) return; diff --git a/songrequests_blazor/Data/LocalDbContext.cs b/songrequests_blazor/Data/LocalDbContext.cs index d16450f..aa3ff03 100644 --- a/songrequests_blazor/Data/LocalDbContext.cs +++ b/songrequests_blazor/Data/LocalDbContext.cs @@ -46,6 +46,10 @@ namespace songrequests_blazor.Data }); + modelBuilder.Entity(entity => { + entity.HasIndex(p => new { p.Library_ID, p.Uri}).IsUnique(); // Datei nur einmal pro Library in DB aufnehmen (eigentlich auch nur einmal insgesamt, aber wir haben hier derzeit keine n:m Beziehung + }); + string AdminRoleGUID = "fc985a25-19ce-48ed-8443-c845f550960a"; string AdminUserGUID = "5c2511d0-6a08-4950-babd-d107996749ba"; diff --git a/songrequests_blazor/Data/Song.cs b/songrequests_blazor/Data/Song.cs index ec010ee..34c1fae 100644 --- a/songrequests_blazor/Data/Song.cs +++ b/songrequests_blazor/Data/Song.cs @@ -1,21 +1,26 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using System; +using System.Composition; using System.Configuration; +using System.Drawing; +using TagLib; namespace songrequests_blazor.Data { public class Song { public int Id { get; set; } + public string Uri { get; set; } = null!; // Tag-Parameter public string Title { get; set; } = null!; public string? Artist { get; set; } public string? Album { get; set; } - public int? Year { get; set; } + public uint? Year { get; set; } public string? Genre { get; set; } - public TimeSpan Length { get; set; } + public long Length { get; set; } + public byte[]? Cover { get; set; } // Statistics @@ -26,5 +31,20 @@ namespace songrequests_blazor.Data public virtual List? Queued { get; set; } + public static Song FromFile(string filename) + { + var mp3file = TagLib.File.Create(filename); + Song song = new(); + song.Uri = filename; + song.Title = mp3file.Tag.Title; + song.Artist = mp3file.Tag.FirstPerformer; + song.Album = mp3file.Tag.Album; + if (mp3file.Tag.Year > 0) song.Year = mp3file.Tag.Year; + song.Genre = mp3file.Tag.FirstGenre; + song.Length = mp3file.Length; + song.Cover = mp3file.Tag.Pictures.Where(p => p.Type == PictureType.FrontCover).FirstOrDefault()?.Data.ToArray(); + return song; + } + } } diff --git a/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.Designer.cs b/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.Designer.cs new file mode 100644 index 0000000..7e89fce --- /dev/null +++ b/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.Designer.cs @@ -0,0 +1,562 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using songrequests_blazor.Data; + +#nullable disable + +namespace songrequests_blazor.Migrations +{ + [DbContext(typeof(LocalDbContext))] + [Migration("20240630183539_ModifiedSongsAddedCoverAndMore")] + partial class ModifiedSongsAddedCoverAndMore + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = "fc985a25-19ce-48ed-8443-c845f550960a", + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(13) + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + + b.UseTphMappingStrategy(); + + b.HasData( + new + { + Id = "5c2511d0-6a08-4950-babd-d107996749ba", + AccessFailedCount = 0, + ConcurrencyStamp = "704e6e3c-2a36-4fda-b08b-eee6df96c7bd", + Email = "admin@admin.com", + EmailConfirmed = false, + LockoutEnabled = false, + NormalizedEmail = "ADMIN@ADMIN.COM", + NormalizedUserName = "ADMIN@ADMIN.COM", + PasswordHash = "AQAAAAIAAYagAAAAEOdhfzdMz0IiXP6IAztlTIpZmIu/kd+Oe6NOtPF4qDu+d22cPQt6qlepPZxXL5jj4g==", + PhoneNumberConfirmed = false, + SecurityStamp = "24955bec-23c8-4bc6-bef3-e7d848e2b6ec", + TwoFactorEnabled = false, + UserName = "admin@admin.com" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + + b.HasData( + new + { + UserId = "5c2511d0-6a08-4950-babd-d107996749ba", + RoleId = "fc985a25-19ce-48ed-8443-c845f550960a" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("QueuedSongs_UserVotes", b => + { + b.Property("UserVotesId") + .HasColumnType("TEXT"); + + b.Property("VotedSongsId") + .HasColumnType("INTEGER"); + + b.HasKey("UserVotesId", "VotedSongsId"); + + b.HasIndex("VotedSongsId"); + + b.ToTable("QueuedSongs_UserVotes"); + }); + + modelBuilder.Entity("Sessions_Libraries", b => + { + b.Property("EnabledLibrariesId") + .HasColumnType("INTEGER"); + + b.Property("UsedInSessionsId") + .HasColumnType("INTEGER"); + + b.HasKey("EnabledLibrariesId", "UsedInSessionsId"); + + b.HasIndex("UsedInSessionsId"); + + b.ToTable("Sessions_Libraries"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ScanPaths") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Added") + .HasColumnType("TEXT"); + + b.Property("AddedBy_User_ID") + .HasColumnType("TEXT"); + + b.Property("Played") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Session_ID") + .HasColumnType("INTEGER"); + + b.Property("Song_ID") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AddedBy_User_ID"); + + b.HasIndex("Session_ID"); + + b.HasIndex("Song_ID"); + + b.ToTable("QueuedSongs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Session", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Album") + .HasColumnType("TEXT"); + + b.Property("Artist") + .HasColumnType("TEXT"); + + b.Property("Cover") + .HasColumnType("BLOB"); + + b.Property("Genre") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Library_ID") + .HasColumnType("INTEGER"); + + b.Property("RequestCount") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Library_ID"); + + b.ToTable("Songs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.User", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.HasDiscriminator().HasValue("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("QueuedSongs_UserVotes", b => + { + b.HasOne("songrequests_blazor.Data.User", null) + .WithMany() + .HasForeignKey("UserVotesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("songrequests_blazor.Data.QueuedSong", null) + .WithMany() + .HasForeignKey("VotedSongsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Sessions_Libraries", b => + { + b.HasOne("songrequests_blazor.Data.Library", null) + .WithMany() + .HasForeignKey("EnabledLibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("songrequests_blazor.Data.Session", null) + .WithMany() + .HasForeignKey("UsedInSessionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b => + { + b.HasOne("songrequests_blazor.Data.User", "AdddedBy") + .WithMany("AddedSongs") + .HasForeignKey("AddedBy_User_ID") + .HasConstraintName("FK_user_addedsongs"); + + b.HasOne("songrequests_blazor.Data.Session", "Session") + .WithMany("QueuedSongs") + .HasForeignKey("Session_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_Session_QueuedSongs"); + + b.HasOne("songrequests_blazor.Data.Song", "Song") + .WithMany("Queued") + .HasForeignKey("Song_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_queuedsongs_song"); + + b.Navigation("AdddedBy"); + + b.Navigation("Session"); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.HasOne("songrequests_blazor.Data.Library", "Library") + .WithMany("Songs") + .HasForeignKey("Library_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_library_songs"); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Library", b => + { + b.Navigation("Songs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Session", b => + { + b.Navigation("QueuedSongs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.Navigation("Queued"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.User", b => + { + b.Navigation("AddedSongs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.cs b/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.cs new file mode 100644 index 0000000..8f79e88 --- /dev/null +++ b/songrequests_blazor/Migrations/20240630183539_ModifiedSongsAddedCoverAndMore.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace songrequests_blazor.Migrations +{ + /// + public partial class ModifiedSongsAddedCoverAndMore : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Length", + table: "Songs", + type: "INTEGER", + nullable: false, + oldClrType: typeof(TimeSpan), + oldType: "TEXT"); + + migrationBuilder.AddColumn( + name: "Cover", + table: "Songs", + type: "BLOB", + nullable: true); + + migrationBuilder.UpdateData( + table: "AspNetUsers", + keyColumn: "Id", + keyValue: "5c2511d0-6a08-4950-babd-d107996749ba", + columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, + values: new object[] { "704e6e3c-2a36-4fda-b08b-eee6df96c7bd", "AQAAAAIAAYagAAAAEOdhfzdMz0IiXP6IAztlTIpZmIu/kd+Oe6NOtPF4qDu+d22cPQt6qlepPZxXL5jj4g==", "24955bec-23c8-4bc6-bef3-e7d848e2b6ec" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Cover", + table: "Songs"); + + migrationBuilder.AlterColumn( + name: "Length", + table: "Songs", + type: "TEXT", + nullable: false, + oldClrType: typeof(long), + oldType: "INTEGER"); + + migrationBuilder.UpdateData( + table: "AspNetUsers", + keyColumn: "Id", + keyValue: "5c2511d0-6a08-4950-babd-d107996749ba", + columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, + values: new object[] { "7d7fc619-fde4-408c-8816-f4ed5f68a60e", "AQAAAAIAAYagAAAAECVwoHKYEXKRa9Sp3UeNNeVSEZOUkCEVkYcF09ZOVlLm9VeDEoX8M9NzwegIk0EWrw==", "10af871c-53d6-4dfc-9e69-5d3a97d8a016" }); + } + } +} diff --git a/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.Designer.cs b/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.Designer.cs new file mode 100644 index 0000000..75510d9 --- /dev/null +++ b/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.Designer.cs @@ -0,0 +1,563 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using songrequests_blazor.Data; + +#nullable disable + +namespace songrequests_blazor.Migrations +{ + [DbContext(typeof(LocalDbContext))] + [Migration("20240630184246_AddUniqueKeyToSongs")] + partial class AddUniqueKeyToSongs + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = "fc985a25-19ce-48ed-8443-c845f550960a", + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Discriminator") + .IsRequired() + .HasMaxLength(13) + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + + b.HasDiscriminator("Discriminator").HasValue("IdentityUser"); + + b.UseTphMappingStrategy(); + + b.HasData( + new + { + Id = "5c2511d0-6a08-4950-babd-d107996749ba", + AccessFailedCount = 0, + ConcurrencyStamp = "0f427274-c406-4fb7-a80f-91fa4fd57cf4", + Email = "admin@admin.com", + EmailConfirmed = false, + LockoutEnabled = false, + NormalizedEmail = "ADMIN@ADMIN.COM", + NormalizedUserName = "ADMIN@ADMIN.COM", + PasswordHash = "AQAAAAIAAYagAAAAEFZWcvwhuJu9C7uVvzbsmUQae3r6sY0py3xFCYortc/Hrb+P1p1fYsCBWp8PRGoIrA==", + PhoneNumberConfirmed = false, + SecurityStamp = "8fddb9be-4f1a-411b-9754-64eaef1bd3a3", + TwoFactorEnabled = false, + UserName = "admin@admin.com" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + + b.HasData( + new + { + UserId = "5c2511d0-6a08-4950-babd-d107996749ba", + RoleId = "fc985a25-19ce-48ed-8443-c845f550960a" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("QueuedSongs_UserVotes", b => + { + b.Property("UserVotesId") + .HasColumnType("TEXT"); + + b.Property("VotedSongsId") + .HasColumnType("INTEGER"); + + b.HasKey("UserVotesId", "VotedSongsId"); + + b.HasIndex("VotedSongsId"); + + b.ToTable("QueuedSongs_UserVotes"); + }); + + modelBuilder.Entity("Sessions_Libraries", b => + { + b.Property("EnabledLibrariesId") + .HasColumnType("INTEGER"); + + b.Property("UsedInSessionsId") + .HasColumnType("INTEGER"); + + b.HasKey("EnabledLibrariesId", "UsedInSessionsId"); + + b.HasIndex("UsedInSessionsId"); + + b.ToTable("Sessions_Libraries"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ScanPaths") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Libraries"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Added") + .HasColumnType("TEXT"); + + b.Property("AddedBy_User_ID") + .HasColumnType("TEXT"); + + b.Property("Played") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("INTEGER"); + + b.Property("Session_ID") + .HasColumnType("INTEGER"); + + b.Property("Song_ID") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AddedBy_User_ID"); + + b.HasIndex("Session_ID"); + + b.HasIndex("Song_ID"); + + b.ToTable("QueuedSongs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Session", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Active") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionCode") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Sessions"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Album") + .HasColumnType("TEXT"); + + b.Property("Artist") + .HasColumnType("TEXT"); + + b.Property("Cover") + .HasColumnType("BLOB"); + + b.Property("Genre") + .HasColumnType("TEXT"); + + b.Property("Length") + .HasColumnType("INTEGER"); + + b.Property("Library_ID") + .HasColumnType("INTEGER"); + + b.Property("RequestCount") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uri") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("Library_ID", "Uri") + .IsUnique(); + + b.ToTable("Songs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.User", b => + { + b.HasBaseType("Microsoft.AspNetCore.Identity.IdentityUser"); + + b.HasDiscriminator().HasValue("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("QueuedSongs_UserVotes", b => + { + b.HasOne("songrequests_blazor.Data.User", null) + .WithMany() + .HasForeignKey("UserVotesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("songrequests_blazor.Data.QueuedSong", null) + .WithMany() + .HasForeignKey("VotedSongsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Sessions_Libraries", b => + { + b.HasOne("songrequests_blazor.Data.Library", null) + .WithMany() + .HasForeignKey("EnabledLibrariesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("songrequests_blazor.Data.Session", null) + .WithMany() + .HasForeignKey("UsedInSessionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b => + { + b.HasOne("songrequests_blazor.Data.User", "AdddedBy") + .WithMany("AddedSongs") + .HasForeignKey("AddedBy_User_ID") + .HasConstraintName("FK_user_addedsongs"); + + b.HasOne("songrequests_blazor.Data.Session", "Session") + .WithMany("QueuedSongs") + .HasForeignKey("Session_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_Session_QueuedSongs"); + + b.HasOne("songrequests_blazor.Data.Song", "Song") + .WithMany("Queued") + .HasForeignKey("Song_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_queuedsongs_song"); + + b.Navigation("AdddedBy"); + + b.Navigation("Session"); + + b.Navigation("Song"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.HasOne("songrequests_blazor.Data.Library", "Library") + .WithMany("Songs") + .HasForeignKey("Library_ID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("FK_library_songs"); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Library", b => + { + b.Navigation("Songs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Session", b => + { + b.Navigation("QueuedSongs"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.Song", b => + { + b.Navigation("Queued"); + }); + + modelBuilder.Entity("songrequests_blazor.Data.User", b => + { + b.Navigation("AddedSongs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.cs b/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.cs new file mode 100644 index 0000000..c7639fb --- /dev/null +++ b/songrequests_blazor/Migrations/20240630184246_AddUniqueKeyToSongs.cs @@ -0,0 +1,51 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace songrequests_blazor.Migrations +{ + /// + public partial class AddUniqueKeyToSongs : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Songs_Library_ID", + table: "Songs"); + + migrationBuilder.UpdateData( + table: "AspNetUsers", + keyColumn: "Id", + keyValue: "5c2511d0-6a08-4950-babd-d107996749ba", + columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, + values: new object[] { "0f427274-c406-4fb7-a80f-91fa4fd57cf4", "AQAAAAIAAYagAAAAEFZWcvwhuJu9C7uVvzbsmUQae3r6sY0py3xFCYortc/Hrb+P1p1fYsCBWp8PRGoIrA==", "8fddb9be-4f1a-411b-9754-64eaef1bd3a3" }); + + migrationBuilder.CreateIndex( + name: "IX_Songs_Library_ID_Uri", + table: "Songs", + columns: new[] { "Library_ID", "Uri" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Songs_Library_ID_Uri", + table: "Songs"); + + migrationBuilder.UpdateData( + table: "AspNetUsers", + keyColumn: "Id", + keyValue: "5c2511d0-6a08-4950-babd-d107996749ba", + columns: new[] { "ConcurrencyStamp", "PasswordHash", "SecurityStamp" }, + values: new object[] { "704e6e3c-2a36-4fda-b08b-eee6df96c7bd", "AQAAAAIAAYagAAAAEOdhfzdMz0IiXP6IAztlTIpZmIu/kd+Oe6NOtPF4qDu+d22cPQt6qlepPZxXL5jj4g==", "24955bec-23c8-4bc6-bef3-e7d848e2b6ec" }); + + migrationBuilder.CreateIndex( + name: "IX_Songs_Library_ID", + table: "Songs", + column: "Library_ID"); + } + } +} diff --git a/songrequests_blazor/Migrations/LocalDbContextModelSnapshot.cs b/songrequests_blazor/Migrations/LocalDbContextModelSnapshot.cs index b0e6c75..18eb0a5 100644 --- a/songrequests_blazor/Migrations/LocalDbContextModelSnapshot.cs +++ b/songrequests_blazor/Migrations/LocalDbContextModelSnapshot.cs @@ -151,15 +151,15 @@ namespace songrequests_blazor.Migrations { Id = "5c2511d0-6a08-4950-babd-d107996749ba", AccessFailedCount = 0, - ConcurrencyStamp = "7d7fc619-fde4-408c-8816-f4ed5f68a60e", + ConcurrencyStamp = "0f427274-c406-4fb7-a80f-91fa4fd57cf4", Email = "admin@admin.com", EmailConfirmed = false, LockoutEnabled = false, NormalizedEmail = "ADMIN@ADMIN.COM", NormalizedUserName = "ADMIN@ADMIN.COM", - PasswordHash = "AQAAAAIAAYagAAAAECVwoHKYEXKRa9Sp3UeNNeVSEZOUkCEVkYcF09ZOVlLm9VeDEoX8M9NzwegIk0EWrw==", + PasswordHash = "AQAAAAIAAYagAAAAEFZWcvwhuJu9C7uVvzbsmUQae3r6sY0py3xFCYortc/Hrb+P1p1fYsCBWp8PRGoIrA==", PhoneNumberConfirmed = false, - SecurityStamp = "10af871c-53d6-4dfc-9e69-5d3a97d8a016", + SecurityStamp = "8fddb9be-4f1a-411b-9754-64eaef1bd3a3", TwoFactorEnabled = false, UserName = "admin@admin.com" }); @@ -373,11 +373,14 @@ namespace songrequests_blazor.Migrations b.Property("Artist") .HasColumnType("TEXT"); + b.Property("Cover") + .HasColumnType("BLOB"); + b.Property("Genre") .HasColumnType("TEXT"); - b.Property("Length") - .HasColumnType("TEXT"); + b.Property("Length") + .HasColumnType("INTEGER"); b.Property("Library_ID") .HasColumnType("INTEGER"); @@ -393,12 +396,13 @@ namespace songrequests_blazor.Migrations .IsRequired() .HasColumnType("TEXT"); - b.Property("Year") + b.Property("Year") .HasColumnType("INTEGER"); b.HasKey("Id"); - b.HasIndex("Library_ID"); + b.HasIndex("Library_ID", "Uri") + .IsUnique(); b.ToTable("Songs"); }); diff --git a/songrequests_blazor/Program.cs b/songrequests_blazor/Program.cs index 2dd8ced..b761f7a 100644 --- a/songrequests_blazor/Program.cs +++ b/songrequests_blazor/Program.cs @@ -59,6 +59,11 @@ if (googleAuth != null) }); } + +// Services +builder.Services.AddSingleton(); +//builder.Services.AddHostedService(); + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/songrequests_blazor/Services/ScanService.cs b/songrequests_blazor/Services/ScanService.cs new file mode 100644 index 0000000..1737241 --- /dev/null +++ b/songrequests_blazor/Services/ScanService.cs @@ -0,0 +1,91 @@ +using System.IO; +using songrequests_blazor.Data; +using System.Collections.Concurrent; +using Microsoft.EntityFrameworkCore; + +namespace songrequests_blazor.Services +{ + public class ScanService : IHostedService, IDisposable + { + public static BlockingCollection Queue { get; set; } = new(); + + IDbContextFactory _dbContextFactory; + + public ScanService(IDbContextFactory contextFactory) + { + _dbContextFactory = contextFactory; + } + + public static void AddToQueue(Library itm) + { + if (itm.ScanPaths == null || itm.ScanPaths.Count == 0) return; + if (!Queue.Contains(itm)) Queue.Add(itm); + } + + public void Dispose() + { + Queue.Dispose(); + } + + private Timer? timer; + + public async Task StartAsync(CancellationToken cancellationToken) + { + await Task.Run(() => + { + timer = new Timer(ScanFiles, cancellationToken, 0, 5000); + }); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + // BlockingCollection leeren + await Task.Run(() => + { + while (Queue.TryTake(out _)) { } + }); + } + + + private void ScanFiles(object? state) + { + CancellationToken token; + if (state != null) + { + token = (CancellationToken)state; + } + else + { + token = new CancellationTokenSource().Token; + } + while (Queue?.Count > 0) + { + + Library lib = Queue.Take(); + List files = new List(); + foreach (var ScanPath in lib.ScanPaths ?? []) + { + Console.WriteLine($"{DateTime.Now:g} Scanning {ScanPath}"); + foreach (string file in Directory.GetFiles(ScanPath, "*.mp3", SearchOption.AllDirectories)) + { + if (lib.Songs == null || !lib.Songs.Any(s => s.Uri.Trim().ToLower() == file.Trim().ToLower())) + { + var song = Song.FromFile(file); + song.Library = lib; + files.Add(song); + } + } + } + + Console.WriteLine($"{DateTime.Now:g} Adding found files to library..."); + using (LocalDbContext db = _dbContextFactory.CreateDbContext()) + { + db.Libraries.Update(lib); + if (lib.Songs == null) lib.Songs = new(); + lib.Songs.AddRange(files); + db.SaveChanges(); + } + } + } + } +} diff --git a/songrequests_blazor/data.db b/songrequests_blazor/data.db index 95e46d1..8a9435d 100644 Binary files a/songrequests_blazor/data.db and b/songrequests_blazor/data.db differ diff --git a/songrequests_blazor/songrequests_blazor.csproj b/songrequests_blazor/songrequests_blazor.csproj index a764cbb..77aecf2 100644 --- a/songrequests_blazor/songrequests_blazor.csproj +++ b/songrequests_blazor/songrequests_blazor.csproj @@ -22,6 +22,8 @@ + +