Logik zum Scannen von Songverzeichnissen und auslesen von MP3-Tags hinzugefügt

This commit is contained in:
BuildTools 2024-06-30 20:44:53 +02:00
parent 4fe401e360
commit eef4754bb0
12 changed files with 1379 additions and 9 deletions

View File

@ -1,7 +1,9 @@
@page "/options/libraries" @page "/options/libraries"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using songrequests_blazor.Data @using songrequests_blazor.Data
@using songrequests_blazor.Services
@inject Microsoft.EntityFrameworkCore.IDbContextFactory<LocalDbContext> DataContextFactory @inject Microsoft.EntityFrameworkCore.IDbContextFactory<LocalDbContext> DataContextFactory
<h3>Bibliotheken</h3> <h3>Bibliotheken</h3>
<a class="btn btn-primary mb-3" href="/options/libraries/edit">Bibliothek anlegen</a> <a class="btn btn-primary mb-3" href="/options/libraries/edit">Bibliothek anlegen</a>
@ -12,6 +14,7 @@
<li class="list-group-item d-flex justify-content-between align-items-center"> <li class="list-group-item d-flex justify-content-between align-items-center">
@itm.Name @itm.Name
<div> <div>
<button type="button" class="btn btn-secondary" @onclick="args => RunScan(itm)">Scan</button>
<a class="btn btn-primary" href="/options/libraries/edit/@itm.Id">Bearbeiten</a> <a class="btn btn-primary" href="/options/libraries/edit/@itm.Id">Bearbeiten</a>
<button type="button" class="btn btn-secondary" @onclick="args => ShowDialog(itm)">Entfernen</button> <button type="button" class="btn btn-secondary" @onclick="args => ShowDialog(itm)">Entfernen</button>
</div> </div>
@ -61,6 +64,12 @@
await modal.CloseAsync(); await modal.CloseAsync();
} }
private void RunScan(Library itm)
{
ScanService.Queue.Add(itm);
}
private async Task RemoveItem() private async Task RemoveItem()
{ {
if (ModalItem == null) return; if (ModalItem == null) return;

View File

@ -46,6 +46,10 @@ namespace songrequests_blazor.Data
}); });
modelBuilder.Entity<Song>(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 AdminRoleGUID = "fc985a25-19ce-48ed-8443-c845f550960a";
string AdminUserGUID = "5c2511d0-6a08-4950-babd-d107996749ba"; string AdminUserGUID = "5c2511d0-6a08-4950-babd-d107996749ba";

View File

@ -1,21 +1,26 @@
using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal;
using System; using System;
using System.Composition;
using System.Configuration; using System.Configuration;
using System.Drawing;
using TagLib;
namespace songrequests_blazor.Data namespace songrequests_blazor.Data
{ {
public class Song public class Song
{ {
public int Id { get; set; } public int Id { get; set; }
public string Uri { get; set; } = null!; public string Uri { get; set; } = null!;
// Tag-Parameter // Tag-Parameter
public string Title { get; set; } = null!; public string Title { get; set; } = null!;
public string? Artist { get; set; } public string? Artist { get; set; }
public string? Album { get; set; } public string? Album { get; set; }
public int? Year { get; set; } public uint? Year { get; set; }
public string? Genre { get; set; } public string? Genre { get; set; }
public TimeSpan Length { get; set; } public long Length { get; set; }
public byte[]? Cover { get; set; }
// Statistics // Statistics
@ -26,5 +31,20 @@ namespace songrequests_blazor.Data
public virtual List<QueuedSong>? Queued { get; set; } public virtual List<QueuedSong>? 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;
}
} }
} }

View File

@ -0,0 +1,562 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("TEXT");
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", (string)null);
b.HasData(
new
{
Id = "fc985a25-19ce-48ed-8443-c845f550960a",
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(13)
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
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<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", (string)null);
b.HasDiscriminator<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("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<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
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", (string)null);
});
modelBuilder.Entity("QueuedSongs_UserVotes", b =>
{
b.Property<string>("UserVotesId")
.HasColumnType("TEXT");
b.Property<int>("VotedSongsId")
.HasColumnType("INTEGER");
b.HasKey("UserVotesId", "VotedSongsId");
b.HasIndex("VotedSongsId");
b.ToTable("QueuedSongs_UserVotes");
});
modelBuilder.Entity("Sessions_Libraries", b =>
{
b.Property<int>("EnabledLibrariesId")
.HasColumnType("INTEGER");
b.Property<int>("UsedInSessionsId")
.HasColumnType("INTEGER");
b.HasKey("EnabledLibrariesId", "UsedInSessionsId");
b.HasIndex("UsedInSessionsId");
b.ToTable("Sessions_Libraries");
});
modelBuilder.Entity("songrequests_blazor.Data.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ScanPaths")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Libraries");
});
modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Added")
.HasColumnType("TEXT");
b.Property<string>("AddedBy_User_ID")
.HasColumnType("TEXT");
b.Property<DateTime?>("Played")
.HasColumnType("TEXT");
b.Property<int>("Position")
.HasColumnType("INTEGER");
b.Property<int>("Session_ID")
.HasColumnType("INTEGER");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SessionCode")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Sessions");
});
modelBuilder.Entity("songrequests_blazor.Data.Song", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Album")
.HasColumnType("TEXT");
b.Property<string>("Artist")
.HasColumnType("TEXT");
b.Property<byte[]>("Cover")
.HasColumnType("BLOB");
b.Property<string>("Genre")
.HasColumnType("TEXT");
b.Property<long>("Length")
.HasColumnType("INTEGER");
b.Property<int>("Library_ID")
.HasColumnType("INTEGER");
b.Property<int>("RequestCount")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint?>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View File

@ -0,0 +1,59 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace songrequests_blazor.Migrations
{
/// <inheritdoc />
public partial class ModifiedSongsAddedCoverAndMore : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<long>(
name: "Length",
table: "Songs",
type: "INTEGER",
nullable: false,
oldClrType: typeof(TimeSpan),
oldType: "TEXT");
migrationBuilder.AddColumn<byte[]>(
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" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Cover",
table: "Songs");
migrationBuilder.AlterColumn<TimeSpan>(
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" });
}
}
}

View File

@ -0,0 +1,563 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<string>("Id")
.HasColumnType("TEXT");
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", (string)null);
b.HasData(
new
{
Id = "fc985a25-19ce-48ed-8443-c845f550960a",
Name = "Administrator",
NormalizedName = "ADMINISTRATOR"
});
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Discriminator")
.IsRequired()
.HasMaxLength(13)
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
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<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", (string)null);
b.HasDiscriminator<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("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<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
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", (string)null);
});
modelBuilder.Entity("QueuedSongs_UserVotes", b =>
{
b.Property<string>("UserVotesId")
.HasColumnType("TEXT");
b.Property<int>("VotedSongsId")
.HasColumnType("INTEGER");
b.HasKey("UserVotesId", "VotedSongsId");
b.HasIndex("VotedSongsId");
b.ToTable("QueuedSongs_UserVotes");
});
modelBuilder.Entity("Sessions_Libraries", b =>
{
b.Property<int>("EnabledLibrariesId")
.HasColumnType("INTEGER");
b.Property<int>("UsedInSessionsId")
.HasColumnType("INTEGER");
b.HasKey("EnabledLibrariesId", "UsedInSessionsId");
b.HasIndex("UsedInSessionsId");
b.ToTable("Sessions_Libraries");
});
modelBuilder.Entity("songrequests_blazor.Data.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ScanPaths")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Libraries");
});
modelBuilder.Entity("songrequests_blazor.Data.QueuedSong", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Added")
.HasColumnType("TEXT");
b.Property<string>("AddedBy_User_ID")
.HasColumnType("TEXT");
b.Property<DateTime?>("Played")
.HasColumnType("TEXT");
b.Property<int>("Position")
.HasColumnType("INTEGER");
b.Property<int>("Session_ID")
.HasColumnType("INTEGER");
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Active")
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("SessionCode")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("Sessions");
});
modelBuilder.Entity("songrequests_blazor.Data.Song", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Album")
.HasColumnType("TEXT");
b.Property<string>("Artist")
.HasColumnType("TEXT");
b.Property<byte[]>("Cover")
.HasColumnType("BLOB");
b.Property<string>("Genre")
.HasColumnType("TEXT");
b.Property<long>("Length")
.HasColumnType("INTEGER");
b.Property<int>("Library_ID")
.HasColumnType("INTEGER");
b.Property<int>("RequestCount")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint?>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View File

@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace songrequests_blazor.Migrations
{
/// <inheritdoc />
public partial class AddUniqueKeyToSongs : Migration
{
/// <inheritdoc />
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);
}
/// <inheritdoc />
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");
}
}
}

View File

@ -151,15 +151,15 @@ namespace songrequests_blazor.Migrations
{ {
Id = "5c2511d0-6a08-4950-babd-d107996749ba", Id = "5c2511d0-6a08-4950-babd-d107996749ba",
AccessFailedCount = 0, AccessFailedCount = 0,
ConcurrencyStamp = "7d7fc619-fde4-408c-8816-f4ed5f68a60e", ConcurrencyStamp = "0f427274-c406-4fb7-a80f-91fa4fd57cf4",
Email = "admin@admin.com", Email = "admin@admin.com",
EmailConfirmed = false, EmailConfirmed = false,
LockoutEnabled = false, LockoutEnabled = false,
NormalizedEmail = "ADMIN@ADMIN.COM", NormalizedEmail = "ADMIN@ADMIN.COM",
NormalizedUserName = "ADMIN@ADMIN.COM", NormalizedUserName = "ADMIN@ADMIN.COM",
PasswordHash = "AQAAAAIAAYagAAAAECVwoHKYEXKRa9Sp3UeNNeVSEZOUkCEVkYcF09ZOVlLm9VeDEoX8M9NzwegIk0EWrw==", PasswordHash = "AQAAAAIAAYagAAAAEFZWcvwhuJu9C7uVvzbsmUQae3r6sY0py3xFCYortc/Hrb+P1p1fYsCBWp8PRGoIrA==",
PhoneNumberConfirmed = false, PhoneNumberConfirmed = false,
SecurityStamp = "10af871c-53d6-4dfc-9e69-5d3a97d8a016", SecurityStamp = "8fddb9be-4f1a-411b-9754-64eaef1bd3a3",
TwoFactorEnabled = false, TwoFactorEnabled = false,
UserName = "admin@admin.com" UserName = "admin@admin.com"
}); });
@ -373,11 +373,14 @@ namespace songrequests_blazor.Migrations
b.Property<string>("Artist") b.Property<string>("Artist")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<byte[]>("Cover")
.HasColumnType("BLOB");
b.Property<string>("Genre") b.Property<string>("Genre")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<TimeSpan>("Length") b.Property<long>("Length")
.HasColumnType("TEXT"); .HasColumnType("INTEGER");
b.Property<int>("Library_ID") b.Property<int>("Library_ID")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -393,12 +396,13 @@ namespace songrequests_blazor.Migrations
.IsRequired() .IsRequired()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int?>("Year") b.Property<uint?>("Year")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("Library_ID"); b.HasIndex("Library_ID", "Uri")
.IsUnique();
b.ToTable("Songs"); b.ToTable("Songs");
}); });

View File

@ -59,6 +59,11 @@ if (googleAuth != null)
}); });
} }
// Services
builder.Services.AddSingleton<IHostedService, ScanService>();
//builder.Services.AddHostedService<ScanService>();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.

View File

@ -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<Library> Queue { get; set; } = new();
IDbContextFactory<LocalDbContext> _dbContextFactory;
public ScanService(IDbContextFactory<LocalDbContext> 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<Song> files = new List<Song>();
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();
}
}
}
}
}

Binary file not shown.

View File

@ -22,6 +22,8 @@
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="8.0.2" />
<PackageReference Include="SpotifyAPI.Web" Version="7.1.1" /> <PackageReference Include="SpotifyAPI.Web" Version="7.1.1" />
<PackageReference Include="System.Drawing.Common" Version="8.0.6" />
<PackageReference Include="taglib-sharp-netstandard2.0" Version="2.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>