diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs
index 3e0a9ecd28..f5c92b3029 100644
--- a/osu.Game/Configuration/DatabasedSetting.cs
+++ b/osu.Game/Configuration/DatabasedSetting.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Configuration
public int? Variant { get; set; }
+ public int? SkinInfoID { get; set; }
+
[Column("Key")]
public string Key { get; set; }
diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs
new file mode 100644
index 0000000000..348c42adb9
--- /dev/null
+++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs
@@ -0,0 +1,498 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20190525060824_SkinSettings")]
+ partial class SkinSettings
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("Status");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.Property("Status");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Key")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("SkinInfoID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("ScoreInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("ScoreInfoID");
+
+ b.ToTable("ScoreFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Accuracy")
+ .HasColumnType("DECIMAL(1,4)");
+
+ b.Property("BeatmapInfoID");
+
+ b.Property("Combo");
+
+ b.Property("Date");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MaxCombo");
+
+ b.Property("ModsJson")
+ .HasColumnName("Mods");
+
+ b.Property("OnlineScoreID");
+
+ b.Property("PP");
+
+ b.Property("Rank");
+
+ b.Property("RulesetID");
+
+ b.Property("StatisticsJson")
+ .HasColumnName("Statistics");
+
+ b.Property("TotalScore");
+
+ b.Property("UserID")
+ .HasColumnName("UserID");
+
+ b.Property("UserString")
+ .HasColumnName("User");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapInfoID");
+
+ b.HasIndex("OnlineScoreID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("ScoreInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Scoring.ScoreInfo")
+ .WithMany("Files")
+ .HasForeignKey("ScoreInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+ .WithMany("Scores")
+ .HasForeignKey("BeatmapInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs
new file mode 100644
index 0000000000..99237419b7
--- /dev/null
+++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs
@@ -0,0 +1,54 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class SkinSettings : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql(@"create table Settings_dg_tmp
+ (
+ ID INTEGER not null
+ constraint PK_Settings
+ primary key autoincrement,
+ Key TEXT not null,
+ RulesetID INTEGER,
+ Value TEXT,
+ Variant INTEGER,
+ SkinInfoID int
+ constraint Settings_SkinInfo_ID_fk
+ references SkinInfo
+ on delete restrict
+ );
+
+ insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings;
+
+ drop table Settings;
+
+ alter table Settings_dg_tmp rename to Settings;
+
+ create index IX_Settings_RulesetID_Variant
+ on Settings (RulesetID, Variant);
+
+ create index Settings_SkinInfoID_index
+ on Settings (SkinInfoID);
+
+ ");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_Settings_SkinInfo_SkinInfoID",
+ table: "Settings");
+
+ migrationBuilder.DropIndex(
+ name: "IX_Settings_SkinInfoID",
+ table: "Settings");
+
+ migrationBuilder.DropColumn(
+ name: "SkinInfoID",
+ table: "Settings");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index f942d357e8..e3f9338b73 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -203,6 +203,8 @@ namespace osu.Game.Migrations
b.Property("RulesetID");
+ b.Property("SkinInfoID");
+
b.Property("StringValue")
.HasColumnName("Value");
@@ -210,6 +212,8 @@ namespace osu.Game.Migrations
b.HasKey("ID");
+ b.HasIndex("SkinInfoID");
+
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
@@ -442,6 +446,13 @@ namespace osu.Game.Migrations
.HasForeignKey("MetadataID");
});
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index 07318b473a..187ea910a7 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using osu.Game.Configuration;
using osu.Game.Database;
namespace osu.Game.Skinning
@@ -19,6 +20,8 @@ namespace osu.Game.Skinning
public List Files { get; set; }
+ public List Settings { get; set; }
+
public bool DeletePending { get; set; }
public string FullName => $"\"{Name}\" by {Creator}";
diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs
index 31cadb0a24..153eeda130 100644
--- a/osu.Game/Skinning/SkinStore.cs
+++ b/osu.Game/Skinning/SkinStore.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Platform;
using osu.Game.Database;
@@ -12,5 +14,9 @@ namespace osu.Game.Skinning
: base(contextFactory, storage)
{
}
+
+ protected override IQueryable AddIncludesForDeletion(IQueryable query) =>
+ base.AddIncludesForDeletion(query)
+ .Include(s => s.Settings); // don't include FileInfo. these are handled by the FileStore itself.
}
}