diff --git a/appveyor.yml b/appveyor.yml
index 21c15724e6..9048428590 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,6 +1,7 @@
# 2017-09-14
clone_depth: 1
version: '{branch}-{build}'
+image: Visual Studio 2017
configuration: Debug
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
@@ -11,7 +12,7 @@ install:
- cmd: git submodule update --init --recursive
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.2/CodeFileSanity.exe
+ - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- cmd: nuget restore
diff --git a/osu-framework b/osu-framework
index b1f36efca5..dbcfa5c244 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit b1f36efca59840da65df788a52107b1674a904c6
+Subproject commit dbcfa5c244555e7901dac7d94eab53b3b04d17e6
diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config
index 824430b24a..0841541f3d 100644
--- a/osu.Desktop/app.config
+++ b/osu.Desktop/app.config
@@ -11,6 +11,10 @@
+
+
+
+
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index fad297fa0a..fb1ca7eb10 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -57,7 +57,6 @@
false
AnyCPU
true
- AllRules.ruleset
false
false
false
@@ -76,7 +75,6 @@
false
AnyCPU
true
- AllRules.ruleset
false
false
@@ -102,7 +100,6 @@
false
6
prompt
- AllRules.ruleset
--tests
@@ -158,6 +155,10 @@
+
+ ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll
+ True
+
diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config
index 58f9102aa1..80eb533644 100644
--- a/osu.Desktop/packages.config
+++ b/osu.Desktop/packages.config
@@ -1,13 +1,14 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/app.config b/osu.Game.Rulesets.Catch/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Catch/app.config
+++ b/osu.Game.Rulesets.Catch/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index d7c86e1f89..f6d30ad3fa 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
beatmap = original;
- BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 20966a75f7..270c264e0c 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index a3173f9784..c38680c3a5 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (drainTime == 0)
drainTime = 10000;
- BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index b98802db69..784df1f293 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Mania
return 0;
}
- protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)));
+ protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index a200ba31e2..9b8ebe0070 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty;
+ BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 3b49d81674..08acd46c57 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -88,18 +88,18 @@ namespace osu.Game.Rulesets.Mania.UI
protected override BeatmapConverter CreateBeatmapConverter()
{
if (IsForCurrentRuleset)
- AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize));
+ AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize));
else
{
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
AvailableColumns = 7;
- else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
- AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6;
+ else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize) >= 5)
+ AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
- AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4;
+ AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 4 ? 5 : 4;
else
- AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7));
+ AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) + 1, 7));
}
return new ManiaBeatmapConverter(IsForCurrentRuleset, AvailableColumns);
diff --git a/osu.Game.Rulesets.Mania/app.config b/osu.Game.Rulesets.Mania/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Mania/app.config
+++ b/osu.Game.Rulesets.Mania/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 50239bf16c..e4960c5ca2 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
+ hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
index adfc946f86..d8dd6c7323 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
if (autoCursorScale && beatmap.Value != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.Difficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
}
cursorContainer.Scale = new Vector2(scale);
diff --git a/osu.Game.Rulesets.Osu/app.config b/osu.Game.Rulesets.Osu/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Osu/app.config
+++ b/osu.Game.Rulesets.Osu/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index ceaecbb555..9b4a6c47a9 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
{
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
- info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
+ info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap converted = base.ConvertBeatmap(original);
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
- double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
@@ -106,12 +106,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
speedAdjustedBeatLength *= speedAdjustment;
// The velocity of the osu! hit object - calculated as the velocity of a slider
- double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+ double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
- double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats);
+ double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / repeats);
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
@@ -154,13 +154,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
Samples = obj.Samples,
IsStrong = strong,
Duration = taikoDuration,
- TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4,
+ TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
};
}
}
else if (endTimeData != null)
{
- double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
+ double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
yield return new Swell
{
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index abdda9676f..752e3bee26 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Taiko.Scoring
protected override void SimulateAutoplay(Beatmap beatmap)
{
- double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98));
+ double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpIncreaseTick = hp_hit_tick;
hpIncreaseGreat = hpMultiplierNormal * hp_hit_great;
hpIncreaseGood = hpMultiplierNormal * hp_hit_good;
- hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
+ hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max);
foreach (var obj in beatmap.HitObjects)
{
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
index 2136d0d86a..14c27bc332 100644
--- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitObjects = new List { new CentreHit() },
BeatmapInfo = new BeatmapInfo
{
- Difficulty = new BeatmapDifficulty(),
+ BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index f0853aef0e..48ee0a5b42 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI
StartTime = time,
};
- barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
+ barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
diff --git a/osu.Game.Rulesets.Taiko/app.config b/osu.Game.Rulesets.Taiko/app.config
index 11af32e2cf..c9d4e44b1a 100644
--- a/osu.Game.Rulesets.Taiko/app.config
+++ b/osu.Game.Rulesets.Taiko/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
index 6bccd47b5c..95b691e07f 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
- var difficulty = beatmap.BeatmapInfo.Difficulty;
+ var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index cd9e765e7f..e5b8c7fe57 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -95,8 +95,6 @@ namespace osu.Game.Tests.Beatmaps.IO
private OsuGameBase loadOsu(GameHost host)
{
- host.Storage.DeleteDatabase(@"client");
-
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
@@ -117,7 +115,7 @@ namespace osu.Game.Tests.Beatmaps.IO
//ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
- Func> queryBeatmaps = () => store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
+ Func> queryBeatmaps = () => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
Func> queryBeatmapSets = () => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526);
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
@@ -157,7 +155,7 @@ namespace osu.Game.Tests.Beatmaps.IO
private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
{
- Action waitAction = () => { while (!result()) Thread.Sleep(20); };
+ Action waitAction = () => { while (!result()) Thread.Sleep(200); };
Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage);
}
}
diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config
index faeaf001de..2f5b13a89d 100644
--- a/osu.Game.Tests/app.config
+++ b/osu.Game.Tests/app.config
@@ -6,6 +6,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 8ebd38022e..51c1b03373 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -39,14 +39,9 @@
True
-
- $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll
-
-
- $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll
-
-
- $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll
+
+ $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll
+ True
diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config
index af47f642e3..ecc44f0c70 100644
--- a/osu.Game.Tests/packages.config
+++ b/osu.Game.Tests/packages.config
@@ -1,11 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 56bb48965f..35b6cc2b02 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps
AuthorString = @"Unknown Creator",
},
Version = @"Normal",
- Difficulty = new BeatmapDifficulty()
+ BaseDifficulty = new BeatmapDifficulty()
};
}
}
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 7c2294cae9..0b0fca8292 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using SQLite.Net.Attributes;
+using System.ComponentModel.DataAnnotations.Schema;
namespace osu.Game.Beatmaps
{
@@ -12,8 +12,9 @@ namespace osu.Game.Beatmaps
///
public const float DEFAULT_DIFFICULTY = 5;
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
+
public float DrainRate { get; set; } = DEFAULT_DIFFICULTY;
public float CircleSize { get; set; } = DEFAULT_DIFFICULTY;
public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY;
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 29c1e3a047..08cef3f934 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -2,18 +2,18 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapInfo : IEquatable, IJsonSerializable
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
//TODO: should be in database
@@ -23,30 +23,24 @@ namespace osu.Game.Beatmaps
public int? OnlineBeatmapID { get; set; }
[JsonProperty("beatmapset_id")]
+ [NotMapped]
public int? OnlineBeatmapSetID { get; set; }
- [ForeignKey(typeof(BeatmapSetInfo))]
public int BeatmapSetInfoID { get; set; }
- [ManyToOne]
+ [Required]
public BeatmapSetInfo BeatmapSet { get; set; }
- [ForeignKey(typeof(BeatmapMetadata))]
- public int BeatmapMetadataID { get; set; }
-
- [OneToOne(CascadeOperations = CascadeOperation.All)]
public BeatmapMetadata Metadata { get; set; }
- [ForeignKey(typeof(BeatmapDifficulty)), NotNull]
public int BaseDifficultyID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.All)]
- public BeatmapDifficulty Difficulty { get; set; }
+ public BeatmapDifficulty BaseDifficulty { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapMetrics Metrics { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapOnlineInfo OnlineInfo { get; set; }
public string Path { get; set; }
@@ -59,7 +53,6 @@ namespace osu.Game.Beatmaps
///
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
///
- [Indexed]
[JsonProperty("file_md5")]
public string MD5Hash { get; set; }
@@ -69,10 +62,8 @@ namespace osu.Game.Beatmaps
public float StackLeniency { get; set; }
public bool SpecialStyle { get; set; }
- [ForeignKey(typeof(RulesetInfo))]
public int RulesetID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
public RulesetInfo Ruleset { get; set; }
public bool LetterboxInBreaks { get; set; }
@@ -101,7 +92,7 @@ namespace osu.Game.Beatmaps
}
}
- [Ignore]
+ [NotMapped]
public int[] Bookmarks { get; set; } = new int[0];
public double DistanceSpacing { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index a998b3eec3..47dbc72837 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -5,8 +5,9 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Linq.Expressions;
+using System.Threading.Tasks;
using Ionic.Zip;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
@@ -15,14 +16,13 @@ using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
+using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IPC;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
-using SQLite.Net;
-using osu.Game.Online.API.Requests;
-using System.Threading.Tasks;
-using osu.Game.Online.API;
namespace osu.Game.Beatmaps
{
@@ -58,9 +58,19 @@ namespace osu.Game.Beatmaps
private readonly Storage storage;
- private readonly FileStore files;
+ private BeatmapStore createBeatmapStore(Func context)
+ {
+ var store = new BeatmapStore(context);
+ store.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
+ store.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
+ store.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
+ store.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
+ return store;
+ }
- private readonly SQLiteConnection connection;
+ private readonly Func createContext;
+
+ private readonly FileStore files;
private readonly RulesetStore rulesets;
@@ -83,22 +93,27 @@ namespace osu.Game.Beatmaps
///
public Func GetStableStorage { private get; set; }
- public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
+ public BeatmapManager(Storage storage, Func context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{
- beatmaps = new BeatmapStore(connection);
- beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
- beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
- beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
- beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
+ createContext = context;
+ importContext = new Lazy(() =>
+ {
+ var c = createContext();
+ c.Database.AutoTransactionsEnabled = false;
+ return c;
+ });
- this.storage = storage;
- this.files = files;
- this.connection = connection;
+ beatmaps = createBeatmapStore(context);
+ files = new FileStore(context, storage);
+
+ this.storage = files.Storage;
this.rulesets = rulesets;
this.api = api;
if (importHost != null)
ipc = new BeatmapIPCChannel(importHost, this);
+
+ beatmaps.Cleanup();
}
///
@@ -156,7 +171,7 @@ namespace osu.Game.Beatmaps
notification.State = ProgressNotificationState.Completed;
}
- private readonly object importLock = new object();
+ private readonly Lazy importContext;
///
/// Import a beatmap from an .
@@ -164,13 +179,29 @@ namespace osu.Game.Beatmaps
/// The beatmap to be imported.
public BeatmapSetInfo Import(ArchiveReader archiveReader)
{
- BeatmapSetInfo set = null;
-
// let's only allow one concurrent import at a time for now.
- lock (importLock)
- connection.RunInTransaction(() => Import(set = importToStorage(archiveReader)));
+ lock (importContext)
+ {
+ var context = importContext.Value;
- return set;
+ using (var transaction = context.Database.BeginTransaction())
+ {
+ // create local stores so we can isolate and thread safely, and share a context/transaction.
+ var iFiles = new FileStore(() => context, storage);
+ var iBeatmaps = createBeatmapStore(() => context);
+
+ BeatmapSetInfo set = importToStorage(iFiles, iBeatmaps, archiveReader);
+
+ if (set.ID == 0)
+ {
+ iBeatmaps.Add(set);
+ context.SaveChanges();
+ }
+
+ transaction.Commit();
+ return set;
+ }
+ }
}
///
@@ -182,7 +213,7 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
- beatmaps.Add(beatmapSetInfo);
+ createBeatmapStore(createContext).Add(beatmapSetInfo);
}
///
@@ -260,10 +291,33 @@ namespace osu.Game.Beatmaps
/// The beatmap set to delete.
public void Delete(BeatmapSetInfo beatmapSet)
{
- if (!beatmaps.Delete(beatmapSet)) return;
+ lock (importContext)
+ {
+ var context = importContext.Value;
- if (!beatmapSet.Protected)
- files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
+ using (var transaction = context.Database.BeginTransaction())
+ {
+ context.ChangeTracker.AutoDetectChangesEnabled = false;
+
+ // re-fetch the beatmap set on the import context.
+ beatmapSet = context.BeatmapSetInfo.Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == beatmapSet.ID);
+
+ // create local stores so we can isolate and thread safely, and share a context/transaction.
+ var iFiles = new FileStore(() => context, storage);
+ var iBeatmaps = createBeatmapStore(() => context);
+
+ if (iBeatmaps.Delete(beatmapSet))
+ {
+ if (!beatmapSet.Protected)
+ iFiles.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
+ }
+
+ context.ChangeTracker.AutoDetectChangesEnabled = true;
+ context.SaveChanges();
+
+ transaction.Commit();
+ }
+ }
}
///
@@ -283,7 +337,7 @@ namespace osu.Game.Beatmaps
/// Is a no-op for already usable beatmaps.
///
/// The beatmap to restore.
- public void Undelete(BeatmapSetInfo beatmapSet)
+ private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Undelete(beatmapSet)) return;
@@ -302,9 +356,6 @@ namespace osu.Game.Beatmaps
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
- lock (beatmaps)
- beatmaps.Populate(beatmapInfo);
-
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
@@ -318,32 +369,12 @@ namespace osu.Game.Beatmaps
return working;
}
- ///
- /// Reset the manager to an empty state.
- ///
- public void Reset()
- {
- lock (beatmaps)
- beatmaps.Reset();
- }
-
///
/// Perform a lookup query on available s.
///
/// The query.
/// The first result for the provided query, or null if no results were found.
- public BeatmapSetInfo QueryBeatmapSet(Func query)
- {
- lock (beatmaps)
- {
- BeatmapSetInfo set = beatmaps.Query().FirstOrDefault(query);
-
- if (set != null)
- beatmaps.Populate(set);
-
- return set;
- }
- }
+ public BeatmapSetInfo QueryBeatmapSet(Func query) => beatmaps.BeatmapSets.FirstOrDefault(query);
///
/// Refresh an existing instance of a from the store.
@@ -357,35 +388,21 @@ namespace osu.Game.Beatmaps
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmapSets(Expression> query)
- {
- return beatmaps.QueryAndPopulate(query);
- }
+ public List QueryBeatmapSets(Func query) => beatmaps.BeatmapSets.Where(query).ToList();
///
/// Perform a lookup query on available s.
///
/// The query.
/// The first result for the provided query, or null if no results were found.
- public BeatmapInfo QueryBeatmap(Func query)
- {
- BeatmapInfo set = beatmaps.Query().FirstOrDefault(query);
-
- if (set != null)
- beatmaps.Populate(set);
-
- return set;
- }
+ public BeatmapInfo QueryBeatmap(Func query) => beatmaps.Beatmaps.FirstOrDefault(query);
///
/// Perform a lookup query on available s.
///
/// The query.
/// Results from the provided query.
- public List QueryBeatmaps(Expression> query)
- {
- lock (beatmaps) return beatmaps.QueryAndPopulate(query);
- }
+ public List QueryBeatmaps(Func query) => beatmaps.Beatmaps.Where(query).ToList();
///
/// Creates an from a valid storage path.
@@ -395,9 +412,9 @@ namespace osu.Game.Beatmaps
private ArchiveReader getReaderFrom(string path)
{
if (ZipFile.IsZipFile(path))
+ // ReSharper disable once InconsistentlySynchronizedField
return new OszArchiveReader(storage.GetStream(path));
- else
- return new LegacyFilesystemReader(path);
+ return new LegacyFilesystemReader(path);
}
///
@@ -406,7 +423,7 @@ namespace osu.Game.Beatmaps
///
/// The beatmap archive to be read.
/// The imported beatmap, or an existing instance if it is already present.
- private BeatmapSetInfo importToStorage(ArchiveReader reader)
+ private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader)
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
@@ -422,13 +439,11 @@ namespace osu.Game.Beatmaps
var hash = hashable.ComputeSHA2Hash();
// check if this beatmap has already been imported and exit early if so.
- BeatmapSetInfo beatmapSet;
- lock (beatmaps)
- beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault();
+ var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash);
if (beatmapSet != null)
{
- Undelete(beatmapSet);
+ undelete(beatmaps, files, beatmapSet);
// ensure all files are present and accessible
foreach (var f in beatmapSet.Files)
@@ -438,6 +453,8 @@ namespace osu.Game.Beatmaps
files.Add(s, false);
}
+ // todo: delete any files which shouldn't exist any more.
+
return beatmapSet;
}
@@ -487,10 +504,11 @@ namespace osu.Game.Beatmaps
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null;
+ RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
+
// TODO: this should be done in a better place once we actually need to dynamically update it.
- beatmap.BeatmapInfo.Ruleset = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID);
- beatmap.BeatmapInfo.StarDifficulty = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap)
- .Calculate() ?? 0;
+ beatmap.BeatmapInfo.Ruleset = ruleset;
+ beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
}
@@ -502,17 +520,10 @@ namespace osu.Game.Beatmaps
///
/// Returns a list of all usable s.
///
- /// Whether returned objects should be pre-populated with all data.
/// A list of available .
- public List GetAllUsableBeatmapSets(bool populate = true)
+ public List GetAllUsableBeatmapSets()
{
- lock (beatmaps)
- {
- if (populate)
- return beatmaps.QueryAndPopulate(b => !b.DeletePending).ToList();
- else
- return beatmaps.Query(b => !b.DeletePending).ToList();
- }
+ return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList();
}
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
@@ -547,7 +558,10 @@ namespace osu.Game.Beatmaps
return beatmap;
}
- catch { return null; }
+ catch
+ {
+ return null;
+ }
}
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
@@ -561,7 +575,10 @@ namespace osu.Game.Beatmaps
{
return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile));
}
- catch { return null; }
+ catch
+ {
+ return null;
+ }
}
protected override Track GetTrack()
@@ -571,7 +588,10 @@ namespace osu.Game.Beatmaps
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new TrackBass(trackData);
}
- catch { return new TrackVirtual(); }
+ catch
+ {
+ return new TrackVirtual();
+ }
}
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
@@ -595,9 +615,9 @@ namespace osu.Game.Beatmaps
public void DeleteAll()
{
- var maps = GetAllUsableBeatmapSets().ToArray();
+ var maps = GetAllUsableBeatmapSets();
- if (maps.Length == 0) return;
+ if (maps.Count == 0) return;
var notification = new ProgressNotification
{
@@ -615,8 +635,8 @@ namespace osu.Game.Beatmaps
// user requested abort
return;
- notification.Text = $"Deleting ({i} of {maps.Length})";
- notification.Progress = (float)++i / maps.Length;
+ notification.Text = $"Deleting ({i} of {maps.Count})";
+ notification.Progress = (float)++i / maps.Count;
Delete(b);
}
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index a062c0a37f..85bcfecfb8 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -1,18 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Users;
-using SQLite.Net.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapMetadata
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
+ [NotMapped]
public int? OnlineBeatmapSetID { get; set; }
public string Title { get; set; }
@@ -20,6 +22,9 @@ namespace osu.Game.Beatmaps
public string Artist { get; set; }
public string ArtistUnicode { get; set; }
+ public List Beatmaps { get; set; }
+ public List BeatmapSets { get; set; }
+
///
/// Helper property to deserialize a username to .
///
diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
index a05362b32d..0e3aa61d9f 100644
--- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
@@ -1,27 +1,24 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
using osu.Game.IO;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapSetFileInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [ForeignKey(typeof(BeatmapSetInfo)), NotNull]
public int BeatmapSetInfoID { get; set; }
- [ForeignKey(typeof(FileInfo)), NotNull]
public int FileInfoID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)]
public FileInfo FileInfo { get; set; }
- [NotNull]
+ [Required]
public string Filename { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index f47affcab8..2dfc4d0fe0 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -2,41 +2,34 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Beatmaps
{
public class BeatmapSetInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int? OnlineBeatmapSetID { get; set; }
- [OneToOne(CascadeOperations = CascadeOperation.All)]
public BeatmapMetadata Metadata { get; set; }
- [NotNull, ForeignKey(typeof(BeatmapMetadata))]
- public int BeatmapMetadataID { get; set; }
-
- [OneToMany(CascadeOperations = CascadeOperation.All)]
public List Beatmaps { get; set; }
- [Ignore]
+ [NotMapped]
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
- [Indexed]
+ [NotMapped]
public bool DeletePending { get; set; }
public string Hash { get; set; }
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
- [OneToMany(CascadeOperations = CascadeOperation.All)]
public List Files { get; set; }
public bool Protected { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index 0f2d8cffa6..8eac35a667 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -2,9 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Game.Database;
-using SQLite.Net;
-using SQLiteNetExtensions.Extensions;
namespace osu.Game.Beatmaps
{
@@ -19,76 +20,23 @@ namespace osu.Game.Beatmaps
public event Action BeatmapHidden;
public event Action BeatmapRestored;
- ///
- /// The current version of this store. Used for migrations (see ).
- /// The initial version is 1.
- ///
- protected override int StoreVersion => 4;
-
- public BeatmapStore(SQLiteConnection connection)
- : base(connection)
+ public BeatmapStore(Func factory)
+ : base(factory)
{
}
- protected override Type[] ValidTypes => new[]
- {
- typeof(BeatmapSetInfo),
- typeof(BeatmapInfo),
- typeof(BeatmapMetadata),
- typeof(BeatmapDifficulty),
- };
-
protected override void Prepare(bool reset = false)
{
if (reset)
{
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- Connection.DropTable();
- }
+ var context = GetContext();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- Connection.CreateTable();
- }
-
- protected override void StartupTasks()
- {
- base.StartupTasks();
- cleanupPendingDeletions();
- }
-
- ///
- /// Perform migrations between two store versions.
- ///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- case 3:
- // Added MD5Hash column to BeatmapInfo
- Connection.MigrateTable();
- break;
- case 4:
- // Added Hidden column to BeatmapInfo
- Connection.MigrateTable();
- break;
- }
+ // https://stackoverflow.com/a/10450893
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo");
+ context.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo");
}
}
@@ -98,10 +46,10 @@ namespace osu.Game.Beatmaps
/// The beatmap to add.
public void Add(BeatmapSetInfo beatmapSet)
{
- Connection.RunInTransaction(() =>
- {
- Connection.InsertOrReplaceWithChildren(beatmapSet, true);
- });
+ var context = GetContext();
+
+ context.BeatmapSetInfo.Attach(beatmapSet);
+ context.SaveChanges();
BeatmapSetAdded?.Invoke(beatmapSet);
}
@@ -113,10 +61,12 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Delete(BeatmapSetInfo beatmapSet)
{
+ var context = GetContext();
+
if (beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = true;
- Connection.Update(beatmapSet);
+ context.SaveChanges();
BeatmapSetRemoved?.Invoke(beatmapSet);
return true;
@@ -129,10 +79,12 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Undelete(BeatmapSetInfo beatmapSet)
{
+ var context = GetContext();
+
if (!beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = false;
- Connection.Update(beatmapSet);
+ context.SaveChanges();
BeatmapSetAdded?.Invoke(beatmapSet);
return true;
@@ -145,10 +97,12 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Hide(BeatmapInfo beatmap)
{
+ var context = GetContext();
+
if (beatmap.Hidden) return false;
beatmap.Hidden = true;
- Connection.Update(beatmap);
+ context.SaveChanges();
BeatmapHidden?.Invoke(beatmap);
return true;
@@ -161,22 +115,49 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Restore(BeatmapInfo beatmap)
{
+ var context = GetContext();
+
if (!beatmap.Hidden) return false;
beatmap.Hidden = false;
- Connection.Update(beatmap);
+ context.SaveChanges();
BeatmapRestored?.Invoke(beatmap);
return true;
}
- private void cleanupPendingDeletions()
+ public override void Cleanup()
{
- Connection.RunInTransaction(() =>
- {
- foreach (var b in QueryAndPopulate(b => b.DeletePending && !b.Protected))
- Connection.Delete(b, true);
- });
+ var context = GetContext();
+
+ var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Metadata);
+
+ // metadata is M-N so we can't rely on cascades
+ context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
+ context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata)));
+
+ // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
+ context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
+
+ // cascades down to beatmaps.
+ context.BeatmapSetInfo.RemoveRange(purgeable);
+ context.SaveChanges();
}
+
+ public IEnumerable BeatmapSets => GetContext().BeatmapSetInfo
+ .Include(s => s.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Files).ThenInclude(f => f.FileInfo);
+
+ public IEnumerable Beatmaps => GetContext().BeatmapInfo
+ .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
+ .Include(b => b.Metadata)
+ .Include(b => b.Ruleset)
+ .Include(b => b.BaseDifficulty);
}
}
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index 60cbf0ac61..bb6a292d9d 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects)
- h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
+ h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
PreprocessHitObjects();
}
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
index d1682a392d..6e5af29799 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs
@@ -83,8 +83,7 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X,
};
- BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList();
- BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
+ BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
index e216f1b83e..c0705d8f61 100644
--- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
+++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Beatmaps.Drawables
new OsuSpriteText
{
Font = @"Exo2.0-MediumItalic",
- Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author}",
+ Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}",
TextSize = 16,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 9a61762fa6..b9376849c1 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
AuthorString = "no one",
},
BeatmapSet = new BeatmapSetInfo(),
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
DrainRate = 0,
CircleSize = 0,
diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
index 81695c3b5a..962c6ad49a 100644
--- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata(),
- Difficulty = new BeatmapDifficulty(),
+ BaseDifficulty = new BeatmapDifficulty(),
},
};
diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
index 3c06180532..d775ab409b 100644
--- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs
@@ -196,7 +196,7 @@ namespace osu.Game.Beatmaps.Formats
{
var pair = splitKeyVal(line, ':');
- var difficulty = beatmap.BeatmapInfo.Difficulty;
+ var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
{
case @"HPDrainRate":
@@ -674,7 +674,7 @@ namespace osu.Game.Beatmaps.Formats
}
foreach (var hitObject in beatmap.HitObjects)
- hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
+ hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
}
private KeyValuePair splitKeyVal(string line, char separator)
diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs
index d8e2e35bd7..be86d35335 100644
--- a/osu.Game/Database/DatabaseBackedStore.cs
+++ b/osu.Game/Database/DatabaseBackedStore.cs
@@ -2,27 +2,21 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Linq.Expressions;
using osu.Framework.Logging;
using osu.Framework.Platform;
-using SQLite.Net;
-using SQLiteNetExtensions.Extensions;
namespace osu.Game.Database
{
public abstract class DatabaseBackedStore
{
protected readonly Storage Storage;
- protected readonly SQLiteConnection Connection;
- protected virtual int StoreVersion => 1;
+ protected readonly Func GetContext;
- protected DatabaseBackedStore(SQLiteConnection connection, Storage storage = null)
+ protected DatabaseBackedStore(Func getContext, Storage storage = null)
{
Storage = storage;
- Connection = connection;
+ GetContext = getContext;
try
{
@@ -33,46 +27,15 @@ namespace osu.Game.Database
Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database...");
Prepare(true);
}
-
- checkMigrations();
- }
-
- private void checkMigrations()
- {
- var storeName = GetType().Name;
-
- var reportedVersion = Connection.Table().Where(s => s.StoreName == storeName).FirstOrDefault() ?? new StoreVersion
- {
- StoreName = storeName,
- Version = 0
- };
-
- if (reportedVersion.Version != StoreVersion)
- PerformMigration(reportedVersion.Version, reportedVersion.Version = StoreVersion);
-
- Connection.InsertOrReplace(reportedVersion);
-
- StartupTasks();
}
///
- /// Called when the database version of this store doesn't match the local version.
- /// Any manual migration operations should be performed in this.
+ /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary.
///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected virtual void PerformMigration(int currentVersion, int targetVersion)
+ public virtual void Cleanup()
{
}
- ///
- /// Perform any common startup tasks. Runs after and .
- ///
- protected virtual void StartupTasks()
- {
-
- }
-
///
/// Prepare this database for use. Tables should be created here.
///
@@ -82,50 +45,5 @@ namespace osu.Game.Database
/// Reset this database to a default state. Undo all changes to database and storage backings.
///
public void Reset() => Prepare(true);
-
-
- public TableQuery Query(Expression> filter = null) where T : class
- {
- checkType(typeof(T));
-
- var query = Connection.Table();
-
- if (filter != null)
- query = query.Where(filter);
-
- return query;
- }
-
- ///
- /// Query and populate results.
- ///
- /// An filter to refine results.
- ///
- public List QueryAndPopulate(Expression> filter)
- where T : class
- {
- checkType(typeof(T));
-
- return Connection.GetAllWithChildren(filter, true);
- }
-
- ///
- /// Populate a database-backed item.
- ///
- ///
- /// Whether population should recurse beyond a single level.
- public void Populate(T item, bool recursive = true)
- {
- checkType(item.GetType());
- Connection.GetChildren(item, recursive);
- }
-
- private void checkType(Type type)
- {
- if (!ValidTypes.Contains(type))
- throw new InvalidOperationException($"The requested operation specified a type of {type}, which is invalid for this {nameof(DatabaseBackedStore)}.");
- }
-
- protected abstract Type[] ValidTypes { get; }
}
}
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
new file mode 100644
index 0000000000..359188b4e2
--- /dev/null
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Platform;
+
+namespace osu.Game.Database
+{
+ public class DatabaseContextFactory
+ {
+ private readonly GameHost host;
+
+ public DatabaseContextFactory(GameHost host)
+ {
+ this.host = host;
+ }
+
+ public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client"));
+ }
+}
diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs
new file mode 100644
index 0000000000..93d23cc1bc
--- /dev/null
+++ b/osu.Game/Database/OsuDbContext.cs
@@ -0,0 +1,245 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps;
+using osu.Game.Input.Bindings;
+using osu.Game.IO;
+using osu.Game.Rulesets;
+using LogLevel = Microsoft.Extensions.Logging.LogLevel;
+
+namespace osu.Game.Database
+{
+ public class OsuDbContext : DbContext
+ {
+ public DbSet BeatmapInfo { get; set; }
+ public DbSet BeatmapDifficulty { get; set; }
+ public DbSet BeatmapMetadata { get; set; }
+ public DbSet BeatmapSetInfo { get; set; }
+ public DbSet DatabasedKeyBinding { get; set; }
+ public DbSet FileInfo { get; set; }
+ public DbSet RulesetInfo { get; set; }
+ private readonly string connectionString;
+
+ private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory());
+
+ static OsuDbContext()
+ {
+ // required to initialise native SQLite libraries on some platforms.
+ SQLitePCL.Batteries_V2.Init();
+ }
+
+ ///
+ /// Create a new in-memory OsuDbContext instance.
+ ///
+ public OsuDbContext()
+ : this("DataSource=:memory:")
+ {
+ // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0).
+ }
+
+ ///
+ /// Create a new OsuDbContext instance.
+ ///
+ /// A valid SQLite connection string.
+ public OsuDbContext(string connectionString)
+ {
+ this.connectionString = connectionString;
+
+ Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10));
+
+ var connection = Database.GetDbConnection();
+ connection.Open();
+ using (var cmd = connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA journal_mode=WAL;";
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ base.OnConfiguring(optionsBuilder);
+ optionsBuilder.UseSqlite(connectionString);
+ optionsBuilder.UseLoggerFactory(logger.Value);
+ }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+
+ modelBuilder.Entity().HasIndex(b => b.MD5Hash);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
+
+ modelBuilder.Entity().HasIndex(b => b.DeletePending);
+ modelBuilder.Entity().HasIndex(b => b.Hash);
+
+ modelBuilder.Entity().HasIndex(b => b.Variant);
+ modelBuilder.Entity().HasIndex(b => b.IntAction);
+
+ modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique();
+ modelBuilder.Entity().HasIndex(b => b.ReferenceCount);
+
+ modelBuilder.Entity().HasIndex(b => b.Available);
+
+ modelBuilder.Entity().HasOne(b => b.BaseDifficulty);
+ }
+
+ private class OsuDbLoggerFactory : ILoggerFactory
+ {
+ #region Disposal
+
+ public void Dispose()
+ {
+ }
+
+ #endregion
+
+ public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
+
+ public void AddProvider(ILoggerProvider provider)
+ {
+ // no-op. called by tooling.
+ }
+
+ private class OsuDbLoggerProvider : ILoggerProvider
+ {
+ #region Disposal
+
+ public void Dispose()
+ {
+ }
+
+ #endregion
+
+ public ILogger CreateLogger(string categoryName) => new OsuDbLogger();
+ }
+
+ private class OsuDbLogger : ILogger
+ {
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ if (logLevel < LogLevel.Information)
+ return;
+
+ Framework.Logging.LogLevel frameworkLogLevel;
+
+ switch (logLevel)
+ {
+ default:
+ frameworkLogLevel = Framework.Logging.LogLevel.Debug;
+ break;
+ case LogLevel.Warning:
+ frameworkLogLevel = Framework.Logging.LogLevel.Important;
+ break;
+ case LogLevel.Error:
+ case LogLevel.Critical:
+ frameworkLogLevel = Framework.Logging.LogLevel.Error;
+ break;
+ }
+
+ Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+#if DEBUG
+ return logLevel > LogLevel.Debug;
+#else
+ return logLevel > LogLevel.Information;
+#endif
+ }
+
+ public IDisposable BeginScope(TState state) => null;
+ }
+ }
+
+ public void Migrate()
+ {
+ migrateFromSqliteNet();
+ Database.Migrate();
+ }
+
+ private void migrateFromSqliteNet()
+ {
+ try
+ {
+ // will fail if EF hasn't touched the database yet.
+ Database.ExecuteSqlCommand("SELECT * FROM __EFMigrationsHistory LIMIT 1");
+ }
+ catch
+ {
+ try
+ {
+ // will fail (intentionally) if we don't have sqlite-net data present.
+ Database.ExecuteSqlCommand("SELECT OnlineBeatmapSetId FROM BeatmapMetadata LIMIT 1");
+
+ try
+ {
+ // we are good to perform messy migration of data!.
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE FileInfo RENAME TO FileInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE KeyBinding RENAME TO KeyBinding_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetInfo RENAME TO BeatmapSetInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapInfo RENAME TO BeatmapInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetFileInfo RENAME TO BeatmapSetFileInfo_Old");
+ Database.ExecuteSqlCommand("ALTER TABLE RulesetInfo RENAME TO RulesetInfo_Old");
+
+ Database.ExecuteSqlCommand("DROP TABLE StoreVersion");
+
+ // perform EF migrations to create sane table structure.
+ Database.Migrate();
+
+ // copy data table by table to new structure, dropping old tables as we go.
+ Database.ExecuteSqlCommand("INSERT INTO FileInfo SELECT * FROM FileInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE FileInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO KeyBinding SELECT ID, [Action], Keys, RulesetID, Variant FROM KeyBinding_Old");
+ Database.ExecuteSqlCommand("DROP TABLE KeyBinding_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapMetadata SELECT ID, Artist, ArtistUnicode, AudioFile, Author, BackgroundFile, PreviewTime, Source, Tags, Title, TitleUnicode FROM BeatmapMetadata_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapMetadata_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapDifficulty SELECT `ID`, `ApproachRate`, `CircleSize`, `DrainRate`, `OverallDifficulty`, `SliderMultiplier`, `SliderTickRate` FROM BeatmapDifficulty_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapDifficulty_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO BeatmapSetInfo SELECT ID, DeletePending, Hash, BeatmapMetadataID, OnlineBeatmapSetID, Protected FROM BeatmapSetInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapSetInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO BeatmapSetFileInfo SELECT ID, BeatmapSetInfoID, FileInfoID, Filename FROM BeatmapSetFileInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapSetFileInfo_Old");
+
+ Database.ExecuteSqlCommand("INSERT INTO RulesetInfo SELECT ID, Available, InstantiationInfo, Name FROM RulesetInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old");
+
+ Database.ExecuteSqlCommand(
+ "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old");
+ Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old");
+ }
+ catch
+ {
+ // if anything went wrong during migration just nuke the database.
+ throw new MigrationFailedException();
+ }
+ }
+ catch (MigrationFailedException e)
+ {
+ throw;
+ }
+ catch
+ {
+ }
+ }
+ }
+ }
+
+ public class MigrationFailedException : Exception
+ {
+ }
+}
diff --git a/osu.Game/Database/StoreVersion.cs b/osu.Game/Database/StoreVersion.cs
deleted file mode 100644
index 00314875a6..0000000000
--- a/osu.Game/Database/StoreVersion.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2007-2017 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using SQLite.Net.Attributes;
-
-namespace osu.Game.Database
-{
- public class StoreVersion
- {
- [PrimaryKey]
- public string StoreName { get; set; }
-
- public int Version { get; set; }
- }
-}
diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs
index 367fd68f7b..31b63ecd21 100644
--- a/osu.Game/IO/FileInfo.cs
+++ b/osu.Game/IO/FileInfo.cs
@@ -1,22 +1,20 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations.Schema;
using System.IO;
-using SQLite.Net.Attributes;
namespace osu.Game.IO
{
public class FileInfo
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [Indexed(Unique = true)]
public string Hash { get; set; }
public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash);
- [Indexed]
public int ReferenceCount { get; set; }
}
}
diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs
index c3d8c1df46..6654fa7cb1 100644
--- a/osu.Game/IO/FileStore.cs
+++ b/osu.Game/IO/FileStore.cs
@@ -4,12 +4,12 @@
using System;
using System.IO;
using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Database;
-using SQLite.Net;
namespace osu.Game.IO
{
@@ -18,75 +18,37 @@ namespace osu.Game.IO
///
public class FileStore : DatabaseBackedStore
{
- private const string prefix = "files";
+ public readonly IResourceStore Store;
- public readonly ResourceStore Store;
+ public Storage Storage => base.Storage;
- protected override int StoreVersion => 2;
-
- public FileStore(SQLiteConnection connection, Storage storage) : base(connection, storage)
+ public FileStore(Func getContext, Storage storage) : base(getContext, storage.GetStorageForDirectory(@"files"))
{
- Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix);
+ Store = new StorageBackedResourceStore(Storage);
}
- protected override Type[] ValidTypes => new[] {
- typeof(FileInfo),
- };
-
protected override void Prepare(bool reset = false)
{
if (reset)
{
- // in earlier versions we stored beatmaps as solid archives, but not any more.
- if (Storage.ExistsDirectory("beatmaps"))
- Storage.DeleteDirectory("beatmaps");
+ if (Storage.ExistsDirectory(string.Empty))
+ Storage.DeleteDirectory(string.Empty);
- if (Storage.ExistsDirectory(prefix))
- Storage.DeleteDirectory(prefix);
-
- Connection.DropTable();
- }
-
- Connection.CreateTable();
- }
-
- protected override void StartupTasks()
- {
- base.StartupTasks();
- deletePending();
- }
-
- ///
- /// Perform migrations between two store versions.
- ///
- /// The current store version. This will be zero on a fresh database initialisation.
- /// The target version which we are migrating to (equal to the current ).
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- }
+ GetContext().Database.ExecuteSqlCommand("DELETE FROM FileInfo");
}
}
public FileInfo Add(Stream data, bool reference = true)
{
+ var context = GetContext();
+
string hash = data.ComputeSHA2Hash();
- var existing = Connection.Table().Where(f => f.Hash == hash).FirstOrDefault();
+ var existing = context.FileInfo.FirstOrDefault(f => f.Hash == hash);
var info = existing ?? new FileInfo { Hash = hash };
- string path = Path.Combine(prefix, info.StoragePath);
+ string path = info.StoragePath;
// we may be re-adding a file to fix missing store entries.
if (!Storage.Exists(path))
@@ -99,62 +61,58 @@ namespace osu.Game.IO
data.Seek(0, SeekOrigin.Begin);
}
- if (existing == null)
- Connection.Insert(info);
-
if (reference || existing == null)
Reference(info);
return info;
}
- public void Reference(params FileInfo[] files)
- {
- Connection.RunInTransaction(() =>
- {
- var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
- {
- var accurateRefCount = Connection.Get(f.First().ID);
- accurateRefCount.ReferenceCount += f.Count();
- return accurateRefCount;
- });
+ public void Reference(params FileInfo[] files) => reference(GetContext(), files);
- Connection.UpdateAll(incrementedFiles);
- });
+ private void reference(OsuDbContext context, FileInfo[] files)
+ {
+ foreach (var f in files.GroupBy(f => f.ID))
+ {
+ var refetch = context.Find(f.First().ID) ?? f.First();
+ refetch.ReferenceCount += f.Count();
+ context.FileInfo.Update(refetch);
+ }
+
+ context.SaveChanges();
}
- public void Dereference(params FileInfo[] files)
- {
- Connection.RunInTransaction(() =>
- {
- var incrementedFiles = files.GroupBy(f => f.ID).Select(f =>
- {
- var accurateRefCount = Connection.Get(f.First().ID);
- accurateRefCount.ReferenceCount -= f.Count();
- return accurateRefCount;
- });
+ public void Dereference(params FileInfo[] files) => dereference(GetContext(), files);
- Connection.UpdateAll(incrementedFiles);
- });
+ private void dereference(OsuDbContext context, FileInfo[] files)
+ {
+ foreach (var f in files.GroupBy(f => f.ID))
+ {
+ var refetch = context.FileInfo.Find(f.Key);
+ refetch.ReferenceCount -= f.Count();
+ context.FileInfo.Update(refetch);
+ }
+
+ context.SaveChanges();
}
- private void deletePending()
+ public override void Cleanup()
{
- Connection.RunInTransaction(() =>
+ var context = GetContext();
+
+ foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
{
- foreach (var f in Query(f => f.ReferenceCount < 1))
+ try
{
- try
- {
- Storage.Delete(Path.Combine(prefix, f.StoragePath));
- Connection.Delete(f);
- }
- catch (Exception e)
- {
- Logger.Error(e, $@"Could not delete beatmap {f}");
- }
+ Storage.Delete(f.StoragePath);
+ context.FileInfo.Remove(f);
}
- });
+ catch (Exception e)
+ {
+ Logger.Error(e, $@"Could not delete beatmap {f}");
+ }
+ }
+
+ context.SaveChanges();
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
index cbf74d6984..c0cecf361d 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs
@@ -1,23 +1,19 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.ComponentModel.DataAnnotations.Schema;
using osu.Framework.Input.Bindings;
-using osu.Game.Rulesets;
-using SQLite.Net.Attributes;
-using SQLiteNetExtensions.Attributes;
namespace osu.Game.Input.Bindings
{
[Table("KeyBinding")]
public class DatabasedKeyBinding : KeyBinding
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
- [ForeignKey(typeof(RulesetInfo))]
public int? RulesetID { get; set; }
- [Indexed]
public int? Variant { get; set; }
[Column("Keys")]
@@ -27,7 +23,6 @@ namespace osu.Game.Input.Bindings
private set { KeyCombination = value; }
}
- [Indexed]
[Column("Action")]
public int IntAction
{
@@ -35,4 +30,4 @@ namespace osu.Game.Input.Bindings
set { Action = value; }
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
index 0a4fcf4389..8a3c65a35e 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs
@@ -51,4 +51,4 @@ namespace osu.Game.Input.Bindings
KeyBindings = store.Query(ruleset?.ID, variant);
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs
index c5ba1683dd..54cf48bc2a 100644
--- a/osu.Game/Input/KeyBindingStore.cs
+++ b/osu.Game/Input/KeyBindingStore.cs
@@ -4,21 +4,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Input.Bindings;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
-using SQLite.Net;
namespace osu.Game.Input
{
public class KeyBindingStore : DatabaseBackedStore
{
- public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null)
- : base(connection, storage)
+ public KeyBindingStore(Func getContext, RulesetStore rulesets, Storage storage = null)
+ : base(getContext, storage)
{
- foreach (var info in rulesets.AllRulesets)
+ foreach (var info in rulesets.AvailableRulesets)
{
var ruleset = info.CreateInstance();
foreach (var variant in ruleset.AvailableVariants)
@@ -28,66 +28,55 @@ namespace osu.Game.Input
public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings);
- protected override int StoreVersion => 3;
-
- protected override void PerformMigration(int currentVersion, int targetVersion)
- {
- base.PerformMigration(currentVersion, targetVersion);
-
- while (currentVersion++ < targetVersion)
- {
- switch (currentVersion)
- {
- case 1:
- case 2:
- case 3:
- // cannot migrate; breaking underlying changes.
- Reset();
- break;
- }
- }
- }
-
protected override void Prepare(bool reset = false)
{
if (reset)
- Connection.DropTable();
-
- Connection.CreateTable();
+ GetContext().Database.ExecuteSqlCommand("DELETE FROM KeyBinding");
}
private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null)
{
- var query = Query(rulesetId, variant);
+ var context = GetContext();
// compare counts in database vs defaults
foreach (var group in defaults.GroupBy(k => k.Action))
{
- int count;
- while (group.Count() > (count = query.Count(k => (int)k.Action == (int)group.Key)))
- {
- var insertable = group.Skip(count).First();
+ int count = query(context, rulesetId, variant).Count(k => (int)k.Action == (int)group.Key);
+ int aimCount = group.Count();
+ if (aimCount <= count)
+ continue;
+
+ foreach (var insertable in group.Skip(count).Take(aimCount - count))
// insert any defaults which are missing.
- Connection.Insert(new DatabasedKeyBinding
+ context.DatabasedKeyBinding.Add(new DatabasedKeyBinding
{
KeyCombination = insertable.KeyCombination,
Action = insertable.Action,
RulesetID = rulesetId,
Variant = variant
});
- }
}
+
+ context.SaveChanges();
}
- protected override Type[] ValidTypes => new[]
+ ///
+ /// Retrieve s for a specified ruleset/variant content.
+ ///
+ /// The ruleset's internal ID.
+ /// An optional variant.
+ ///
+ public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(GetContext(), rulesetId, variant);
+
+ private IEnumerable query(OsuDbContext context, int? rulesetId = null, int? variant = null) =>
+ context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant);
+
+ public void Update(KeyBinding keyBinding)
{
- typeof(DatabasedKeyBinding)
- };
-
- public IEnumerable Query(int? rulesetId = null, int? variant = null) =>
- Query(b => b.RulesetID == rulesetId && b.Variant == variant);
-
- public void Update(KeyBinding keyBinding) => Connection.Update(keyBinding);
+ var context = GetContext();
+ context.Update(keyBinding);
+ context.SaveChanges();
+ }
}
}
diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs
new file mode 100644
index 0000000000..0820041643
--- /dev/null
+++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs
@@ -0,0 +1,293 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using osu.Game.Database;
+using System;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20171019041408_InitialCreate")]
+ partial class InitialCreate
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+
+ 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("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("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.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ 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("Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ 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.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.ToTable("RulesetInfo");
+ });
+
+ 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");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20171019041408_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs
new file mode 100644
index 0000000000..23e5b6f8bb
--- /dev/null
+++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs
@@ -0,0 +1,313 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using System;
+using System.Collections.Generic;
+
+namespace osu.Game.Migrations
+{
+ public partial class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "BeatmapDifficulty",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ ApproachRate = table.Column(type: "REAL", nullable: false),
+ CircleSize = table.Column(type: "REAL", nullable: false),
+ DrainRate = table.Column(type: "REAL", nullable: false),
+ OverallDifficulty = table.Column(type: "REAL", nullable: false),
+ SliderMultiplier = table.Column(type: "REAL", nullable: false),
+ SliderTickRate = table.Column(type: "REAL", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapMetadata",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Artist = table.Column(type: "TEXT", nullable: true),
+ ArtistUnicode = table.Column(type: "TEXT", nullable: true),
+ AudioFile = table.Column(type: "TEXT", nullable: true),
+ Author = table.Column(type: "TEXT", nullable: true),
+ BackgroundFile = table.Column(type: "TEXT", nullable: true),
+ PreviewTime = table.Column(type: "INTEGER", nullable: false),
+ Source = table.Column(type: "TEXT", nullable: true),
+ Tags = table.Column(type: "TEXT", nullable: true),
+ Title = table.Column(type: "TEXT", nullable: true),
+ TitleUnicode = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapMetadata", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "FileInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ ReferenceCount = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_FileInfo", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "KeyBinding",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Action = table.Column(type: "INTEGER", nullable: false),
+ Keys = table.Column(type: "TEXT", nullable: true),
+ RulesetID = table.Column(type: "INTEGER", nullable: true),
+ Variant = table.Column(type: "INTEGER", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_KeyBinding", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "RulesetInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Available = table.Column(type: "INTEGER", nullable: false),
+ InstantiationInfo = table.Column(type: "TEXT", nullable: true),
+ Name = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_RulesetInfo", x => x.ID);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapSetInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ DeletePending = table.Column(type: "INTEGER", nullable: false),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ MetadataID = table.Column(type: "INTEGER", nullable: true),
+ OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true),
+ Protected = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID",
+ column: x => x.MetadataID,
+ principalTable: "BeatmapMetadata",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ AudioLeadIn = table.Column(type: "INTEGER", nullable: false),
+ BaseDifficultyID = table.Column(type: "INTEGER", nullable: false),
+ BeatDivisor = table.Column(type: "INTEGER", nullable: false),
+ BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false),
+ Countdown = table.Column(type: "INTEGER", nullable: false),
+ DistanceSpacing = table.Column(type: "REAL", nullable: false),
+ GridSize = table.Column(type: "INTEGER", nullable: false),
+ Hash = table.Column(type: "TEXT", nullable: true),
+ Hidden = table.Column(type: "INTEGER", nullable: false),
+ LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false),
+ MD5Hash = table.Column(type: "TEXT", nullable: true),
+ MetadataID = table.Column(type: "INTEGER", nullable: true),
+ OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true),
+ Path = table.Column(type: "TEXT", nullable: true),
+ RulesetID = table.Column(type: "INTEGER", nullable: false),
+ SpecialStyle = table.Column(type: "INTEGER", nullable: false),
+ StackLeniency = table.Column(type: "REAL", nullable: false),
+ StarDifficulty = table.Column(type: "REAL", nullable: false),
+ StoredBookmarks = table.Column(type: "TEXT", nullable: true),
+ TimelineZoom = table.Column(type: "REAL", nullable: false),
+ Version = table.Column(type: "TEXT", nullable: true),
+ WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID",
+ column: x => x.BaseDifficultyID,
+ principalTable: "BeatmapDifficulty",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID",
+ column: x => x.BeatmapSetInfoID,
+ principalTable: "BeatmapSetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID",
+ column: x => x.MetadataID,
+ principalTable: "BeatmapMetadata",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_BeatmapInfo_RulesetInfo_RulesetID",
+ column: x => x.RulesetID,
+ principalTable: "RulesetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "BeatmapSetFileInfo",
+ columns: table => new
+ {
+ ID = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false),
+ FileInfoID = table.Column(type: "INTEGER", nullable: false),
+ Filename = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_BeatmapSetFileInfo", x => x.ID);
+ table.ForeignKey(
+ name: "FK_BeatmapSetFileInfo_BeatmapSetInfo_BeatmapSetInfoID",
+ column: x => x.BeatmapSetInfoID,
+ principalTable: "BeatmapSetInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_BeatmapSetFileInfo_FileInfo_FileInfoID",
+ column: x => x.FileInfoID,
+ principalTable: "FileInfo",
+ principalColumn: "ID",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_BaseDifficultyID",
+ table: "BeatmapInfo",
+ column: "BaseDifficultyID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_BeatmapSetInfoID",
+ table: "BeatmapInfo",
+ column: "BeatmapSetInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_Hash",
+ table: "BeatmapInfo",
+ column: "Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MD5Hash",
+ table: "BeatmapInfo",
+ column: "MD5Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_MetadataID",
+ table: "BeatmapInfo",
+ column: "MetadataID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapInfo_RulesetID",
+ table: "BeatmapInfo",
+ column: "RulesetID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID",
+ table: "BeatmapSetFileInfo",
+ column: "BeatmapSetInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetFileInfo_FileInfoID",
+ table: "BeatmapSetFileInfo",
+ column: "FileInfoID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_DeletePending",
+ table: "BeatmapSetInfo",
+ column: "DeletePending");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_Hash",
+ table: "BeatmapSetInfo",
+ column: "Hash");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_BeatmapSetInfo_MetadataID",
+ table: "BeatmapSetInfo",
+ column: "MetadataID");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FileInfo_Hash",
+ table: "FileInfo",
+ column: "Hash",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FileInfo_ReferenceCount",
+ table: "FileInfo",
+ column: "ReferenceCount");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_KeyBinding_Action",
+ table: "KeyBinding",
+ column: "Action");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_KeyBinding_Variant",
+ table: "KeyBinding",
+ column: "Variant");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_RulesetInfo_Available",
+ table: "RulesetInfo",
+ column: "Available");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "BeatmapInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapSetFileInfo");
+
+ migrationBuilder.DropTable(
+ name: "KeyBinding");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapDifficulty");
+
+ migrationBuilder.DropTable(
+ name: "RulesetInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapSetInfo");
+
+ migrationBuilder.DropTable(
+ name: "FileInfo");
+
+ migrationBuilder.DropTable(
+ name: "BeatmapMetadata");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
new file mode 100644
index 0000000000..0576242648
--- /dev/null
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -0,0 +1,292 @@
+//
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage;
+using osu.Game.Database;
+using System;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ partial class OsuDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
+
+ 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("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("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.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ 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("Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ 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.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.ToTable("RulesetInfo");
+ });
+
+ 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");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 93eb1d76df..e7ad77d099 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -16,7 +16,6 @@ using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API;
-using SQLite.Net;
using osu.Framework.Graphics.Performance;
using osu.Game.Database;
using osu.Game.Input;
@@ -81,23 +80,28 @@ namespace osu.Game
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
- private SQLiteConnection createConnection()
- {
- var conn = Host.Storage.GetDatabase(@"client");
- conn.BusyTimeout = new TimeSpan(TimeSpan.TicksPerSecond * 10);
- return conn;
- }
-
- private SQLiteConnection connection;
+ private DatabaseContextFactory contextFactory;
[BackgroundDependencyLoader]
private void load()
{
+ dependencies.Cache(contextFactory = new DatabaseContextFactory(Host));
+
dependencies.Cache(this);
dependencies.Cache(LocalConfig);
- connection = createConnection();
- connection.CreateTable();
+ try
+ {
+ using (var context = contextFactory.GetContext())
+ context.Migrate();
+ }
+ catch (MigrationFailedException)
+ {
+ using (var context = contextFactory.GetContext())
+ context.Database.EnsureDeleted();
+ using (var context = contextFactory.GetContext())
+ context.Migrate();
+ }
dependencies.Cache(API = new APIAccess
{
@@ -105,11 +109,11 @@ namespace osu.Game
Token = LocalConfig.Get(OsuSetting.Token)
});
- dependencies.Cache(RulesetStore = new RulesetStore(connection));
- dependencies.Cache(FileStore = new FileStore(connection, Host.Storage));
- dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, API, Host));
- dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager, RulesetStore));
- dependencies.Cache(KeyBindingStore = new KeyBindingStore(connection, RulesetStore));
+ dependencies.Cache(RulesetStore = new RulesetStore(contextFactory.GetContext));
+ dependencies.Cache(FileStore = new FileStore(contextFactory.GetContext, Host.Storage));
+ dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host));
+ dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore));
+ dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore));
dependencies.Cache(new OsuColour());
//this completely overrides the framework default. will need to change once we make a proper FontStore.
@@ -165,6 +169,8 @@ namespace osu.Game
};
API.Register(this);
+
+ FileStore.Cleanup();
}
private WorkingBeatmap lastBeatmap;
@@ -207,10 +213,7 @@ namespace osu.Game
// TODO: This is temporary until we reimplement the local FPS display.
// It's just to allow end-users to access the framework FPS display without knowing the shortcut key.
fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay);
- fpsDisplayVisible.ValueChanged += val =>
- {
- FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None;
- };
+ fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; };
fpsDisplayVisible.TriggerChange();
}
@@ -236,8 +239,6 @@ namespace osu.Game
LocalConfig.Save();
}
- connection?.Dispose();
-
base.Dispose(isDisposing);
}
}
diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs
index 28d26d0641..9b52cfd367 100644
--- a/osu.Game/Overlays/Direct/FilterControl.cs
+++ b/osu.Game/Overlays/Direct/FilterControl.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Direct
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
Ruleset.BindTo(game?.Ruleset ?? new Bindable { Value = rulesets.GetRuleset(0) });
- foreach (var r in rulesets.AllRulesets)
+ foreach (var r in rulesets.AvailableRulesets)
{
modeButtons.Add(new RulesetToggleButton(Ruleset, r));
}
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
index bd69403831..128b5e2f09 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs
@@ -37,8 +37,10 @@ namespace osu.Game.Overlays.KeyBinding
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
{
+ int intKey = (int)defaultGroup.Key;
+
// one row per valid action.
- Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals((int)defaultGroup.Key)))
+ Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey)))
{
AllowMainMouseButtons = Ruleset != null,
Defaults = defaultGroup.Select(d => d.KeyCombination)
diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs
index 72c653030c..4394d0fec0 100644
--- a/osu.Game/Overlays/KeyBindingOverlay.cs
+++ b/osu.Game/Overlays/KeyBindingOverlay.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Overlays
{
AddSection(new GlobalKeyBindingsSection(global));
- foreach (var ruleset in rulesets.AllRulesets)
+ foreach (var ruleset in rulesets.AvailableRulesets)
AddSection(new RulesetBindingsSection(ruleset));
}
diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
index eb643f390f..9ff21dfdd4 100644
--- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs
+++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Mods
if (osu != null)
Ruleset.BindTo(osu.Ruleset);
else
- Ruleset.Value = rulesets.AllRulesets.First();
+ Ruleset.Value = rulesets.AvailableRulesets.First();
Ruleset.ValueChanged += rulesetChanged;
Ruleset.TriggerChange();
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index 326cb582e2..035a3c7a13 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- foreach(Ruleset ruleset in rulesets.AllRulesets.Select(info => info.CreateInstance()))
+ foreach(Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
{
SettingsSubsection section = ruleset.CreateSettings();
if (section != null)
diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs
index cb1c861ee7..bf417a2fac 100644
--- a/osu.Game/Overlays/Settings/SettingsFooter.cs
+++ b/osu.Game/Overlays/Settings/SettingsFooter.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings
var modes = new List();
- foreach (var ruleset in rulesets.AllRulesets)
+ foreach (var ruleset in rulesets.AvailableRulesets)
{
var icon = new ConstrainedIconContainer
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 60c1261190..da72ae0347 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Toolbar
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets, OsuGame game)
{
- foreach (var r in rulesets.AllRulesets)
+ foreach (var r in rulesets.AvailableRulesets)
{
modeButtons.Add(new ToolbarModeButton
{
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index 740369b1b6..17f07158df 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -2,22 +2,19 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using SQLite.Net.Attributes;
+using System.ComponentModel.DataAnnotations.Schema;
namespace osu.Game.Rulesets
{
public class RulesetInfo : IEquatable
{
- [PrimaryKey, AutoIncrement]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int? ID { get; set; }
- [Indexed(Unique = true)]
public string Name { get; set; }
- [Indexed(Unique = true)]
public string InstantiationInfo { get; set; }
- [Indexed]
public bool Available { get; set; }
public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 407a5f7c3c..7d982eb39e 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -6,8 +6,8 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using Microsoft.EntityFrameworkCore;
using osu.Game.Database;
-using SQLite.Net;
namespace osu.Game.Rulesets
{
@@ -18,12 +18,6 @@ namespace osu.Game.Rulesets
{
private static readonly Dictionary loaded_assemblies = new Dictionary();
- public IEnumerable AllRulesets => Query().Where(r => r.Available);
-
- public RulesetStore(SQLiteConnection connection) : base(connection)
- {
- }
-
static RulesetStore()
{
AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
@@ -32,59 +26,78 @@ namespace osu.Game.Rulesets
loadRulesetFromFile(file);
}
+ public RulesetStore(Func factory)
+ : base(factory)
+ {
+ }
+
+ ///
+ /// Retrieve a ruleset using a known ID.
+ ///
+ /// The ruleset's internal ID.
+ /// A ruleset, if available, else null.
+ public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id);
+
+ ///
+ /// All available rulesets.
+ ///
+ public IEnumerable AvailableRulesets => GetContext().RulesetInfo.Where(r => r.Available);
+
private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
private const string ruleset_library_prefix = "osu.Game.Rulesets";
protected override void Prepare(bool reset = false)
{
- Connection.CreateTable();
+ var context = GetContext();
if (reset)
{
- Connection.DeleteAll();
+ context.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo");
}
- var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo()));
+ var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())).ToList();
- Connection.RunInTransaction(() =>
+ //add all legacy modes in correct order
+ foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
{
- //add all legacy modes in correct order
- foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
+ var rulesetInfo = createRulesetInfo(r);
+ if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null)
{
- Connection.InsertOrReplace(createRulesetInfo(r));
+ context.RulesetInfo.Add(rulesetInfo);
}
+ }
- //add any other modes
- foreach (var r in instances.Where(r => r.LegacyID < 0))
- {
- var us = createRulesetInfo(r);
+ context.SaveChanges();
- var existing = Query().Where(ri => ri.InstantiationInfo == us.InstantiationInfo).FirstOrDefault();
-
- if (existing == null)
- Connection.Insert(us);
- }
- });
-
- Connection.RunInTransaction(() =>
+ //add any other modes
+ foreach (var r in instances.Where(r => r.LegacyID < 0))
{
- //perform a consistency check
- foreach (var r in Query())
- {
- try
- {
- r.CreateInstance();
- r.Available = true;
- }
- catch
- {
- r.Available = false;
- }
+ var us = createRulesetInfo(r);
- Connection.Update(r);
+ var existing = context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo);
+
+ if (existing == null)
+ context.RulesetInfo.Add(us);
+ }
+
+ context.SaveChanges();
+
+ //perform a consistency check
+ foreach (var r in context.RulesetInfo)
+ {
+ try
+ {
+ r.CreateInstance();
+ r.Available = true;
}
- });
+ catch
+ {
+ r.Available = false;
+ }
+ }
+
+ context.SaveChanges();
}
private static void loadRulesetFromFile(string file)
@@ -99,7 +112,9 @@ namespace osu.Game.Rulesets
var assembly = Assembly.LoadFrom(file);
loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset)));
}
- catch (Exception) { }
+ catch (Exception)
+ {
+ }
}
private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo
@@ -108,9 +123,5 @@ namespace osu.Game.Rulesets
InstantiationInfo = ruleset.GetType().AssemblyQualifiedName,
ID = ruleset.LegacyID
};
-
- protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) };
-
- public RulesetInfo GetRuleset(int id) => Query().First(r => r.ID == id);
}
}
diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs
index c06d31e38f..67a8e5372e 100644
--- a/osu.Game/Rulesets/Scoring/ScoreStore.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs
@@ -11,7 +11,6 @@ using osu.Game.IO.Legacy;
using osu.Game.IPC;
using osu.Game.Rulesets.Replays;
using SharpCompress.Compressors.LZMA;
-using SQLite.Net;
namespace osu.Game.Rulesets.Scoring
{
@@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ScoreIPCChannel ipc;
- public ScoreStore(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(connection)
+ public ScoreStore(Storage storage, Func factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
{
this.storage = storage;
this.beatmaps = beatmaps;
@@ -148,7 +147,5 @@ namespace osu.Game.Rulesets.Scoring
protected override void Prepare(bool reset = false)
{
}
-
- protected override Type[] ValidTypes => new[] { typeof(Score) };
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index 729df02ffd..6f53b76031 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -172,11 +172,11 @@ namespace osu.Game.Rulesets.UI
// Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType())
- mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty);
+ mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
// Apply defaults
foreach (var h in Beatmap.HitObjects)
- h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
+ h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
// Post-process the beatmap
processor.PostProcess(Beatmap);
diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs
index 3aeef4bbc9..ee84cf2d30 100644
--- a/osu.Game/Screens/Menu/Intro.cs
+++ b/osu.Game/Screens/Menu/Intro.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Screens.Menu
{
private readonly OsuLogo logo;
- private const string menu_music_beatmap_hash = "715a09144f885d746644c1983e285044";
+ private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83";
///
/// Whether we have loaded the menu previously.
@@ -76,7 +76,7 @@ namespace osu.Game.Screens.Menu
if (!menuMusic)
{
- var sets = beatmaps.GetAllUsableBeatmapSets(false);
+ var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count > 0)
setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
}
@@ -89,9 +89,7 @@ namespace osu.Game.Screens.Menu
{
// we need to import the default menu background beatmap
setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz")));
-
setInfo.Protected = true;
- beatmaps.Delete(setInfo);
}
}
@@ -101,6 +99,7 @@ namespace osu.Game.Screens.Menu
welcome = audio.Sample.Get(@"welcome");
seeya = audio.Sample.Get(@"seeya");
+ beatmaps.Delete(setInfo);
}
protected override void OnEntering(Screen last)
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index c72f599955..b8cc9782ca 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -33,10 +33,7 @@ namespace osu.Game.Screens.Select
public IEnumerable Beatmaps
{
- get
- {
- return groups.Select(g => g.BeatmapSet);
- }
+ get { return groups.Select(g => g.BeatmapSet); }
set
{
@@ -100,12 +97,10 @@ namespace osu.Game.Screens.Select
public void AddBeatmap(BeatmapSetInfo beatmapSet)
{
- var group = createGroup(beatmapSet);
-
- //for the time being, let's completely load the difficulty panels in the background.
- //this likely won't scale so well, but allows us to completely async the loading flow.
- Schedule(delegate
+ Schedule(() =>
{
+ var group = createGroup(beatmapSet);
+
addGroup(group);
computeYPositions();
if (selectedGroup == null)
@@ -113,7 +108,10 @@ namespace osu.Game.Screens.Select
});
}
- public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID));
+ public void RemoveBeatmap(BeatmapSetInfo beatmapSet)
+ {
+ Schedule(() => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)));
+ }
internal void UpdateBeatmap(BeatmapInfo beatmap)
{
@@ -519,7 +517,7 @@ namespace osu.Game.Screens.Select
float drawHeight = DrawHeight;
// Remove all panels that should no longer be on-screen
- scrollableContent.RemoveAll(delegate (Panel p)
+ scrollableContent.RemoveAll(delegate(Panel p)
{
float panelPosY = p.Position.Y;
bool remove = panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !p.IsPresent;
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index a98362e89c..d7c509d979 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -264,7 +264,7 @@ namespace osu.Game.Screens.Select
advanced.Beatmap = new BeatmapInfo
{
StarDifficulty = 0,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 0,
DrainRate = 0,
diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs
index f1215ab33d..3c9cffadfb 100644
--- a/osu.Game/Screens/Select/Details/AdvancedStats.cs
+++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs
@@ -32,17 +32,17 @@ namespace osu.Game.Screens.Select.Details
if ((Beatmap?.Ruleset?.ID ?? 0) == 3)
{
firstValue.Title = "Key Amount";
- firstValue.Value = (int)Math.Round(Beatmap?.Difficulty?.CircleSize ?? 0);
+ firstValue.Value = (int)Math.Round(Beatmap?.BaseDifficulty?.CircleSize ?? 0);
}
else
{
firstValue.Title = "Circle Size";
- firstValue.Value = Beatmap?.Difficulty?.CircleSize ?? 0;
+ firstValue.Value = Beatmap?.BaseDifficulty?.CircleSize ?? 0;
}
- hpDrain.Value = beatmap.Difficulty?.DrainRate ?? 0;
- accuracy.Value = beatmap.Difficulty?.OverallDifficulty ?? 0;
- approachRate.Value = beatmap.Difficulty?.ApproachRate ?? 0;
+ hpDrain.Value = beatmap.BaseDifficulty?.DrainRate ?? 0;
+ accuracy.Value = beatmap.BaseDifficulty?.OverallDifficulty ?? 0;
+ approachRate.Value = beatmap.BaseDifficulty?.ApproachRate ?? 0;
starDifficulty.Value = (float)beatmap.StarDifficulty;
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index b11613634a..e11eed7040 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select
{
public abstract class SongSelect : OsuScreen
{
- private BeatmapManager manager;
+ private BeatmapManager beatmaps;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap();
private readonly BeatmapCarousel carousel;
@@ -108,9 +108,9 @@ namespace osu.Game.Screens.Select
SelectionChanged = carouselSelectionChanged,
BeatmapsChanged = carouselBeatmapsLoaded,
DeleteRequested = promptDelete,
- RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); },
+ RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); },
EditRequested = editRequested,
- HideDifficultyRequested = b => manager.Hide(b),
+ HideDifficultyRequested = b => beatmaps.Hide(b),
StartRequested = () => carouselRaisedStart(),
});
Add(FilterControl = new FilterControl
@@ -171,16 +171,16 @@ namespace osu.Game.Screens.Select
BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue);
}
- if (manager == null)
- manager = beatmaps;
+ if (this.beatmaps == null)
+ this.beatmaps = beatmaps;
if (osu != null)
Ruleset.BindTo(osu.Ruleset);
- manager.BeatmapSetAdded += onBeatmapSetAdded;
- manager.BeatmapSetRemoved += onBeatmapSetRemoved;
- manager.BeatmapHidden += onBeatmapHidden;
- manager.BeatmapRestored += onBeatmapRestored;
+ this.beatmaps.BeatmapSetAdded += onBeatmapSetAdded;
+ this.beatmaps.BeatmapSetRemoved += onBeatmapSetRemoved;
+ this.beatmaps.BeatmapHidden += onBeatmapHidden;
+ this.beatmaps.BeatmapRestored += onBeatmapRestored;
dialogOverlay = dialog;
@@ -189,7 +189,7 @@ namespace osu.Game.Screens.Select
initialAddSetsTask = new CancellationTokenSource();
- carousel.Beatmaps = manager.GetAllUsableBeatmapSets();
+ carousel.Beatmaps = this.beatmaps.GetAllUsableBeatmapSets();
Beatmap.ValueChanged += beatmap_ValueChanged;
@@ -199,7 +199,7 @@ namespace osu.Game.Screens.Select
private void editRequested(BeatmapInfo beatmap)
{
- Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap);
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap);
Push(new Editor());
}
@@ -248,7 +248,7 @@ namespace osu.Game.Screens.Select
{
bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value.BeatmapInfo.BeatmapSetInfoID;
- Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap);
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap);
ensurePlayingSelected(preview);
}
@@ -357,12 +357,12 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
- if (manager != null)
+ if (beatmaps != null)
{
- manager.BeatmapSetAdded -= onBeatmapSetAdded;
- manager.BeatmapSetRemoved -= onBeatmapSetRemoved;
- manager.BeatmapHidden -= onBeatmapHidden;
- manager.BeatmapRestored -= onBeatmapRestored;
+ beatmaps.BeatmapSetAdded -= onBeatmapSetAdded;
+ beatmaps.BeatmapSetRemoved -= onBeatmapSetRemoved;
+ beatmaps.BeatmapHidden -= onBeatmapHidden;
+ beatmaps.BeatmapRestored -= onBeatmapRestored;
}
initialAddSetsTask?.Cancel();
diff --git a/osu.Game/Tests/Platform/TestStorage.cs b/osu.Game/Tests/Platform/TestStorage.cs
index 9f76df2a58..e583183d46 100644
--- a/osu.Game/Tests/Platform/TestStorage.cs
+++ b/osu.Game/Tests/Platform/TestStorage.cs
@@ -1,12 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using osu.Framework;
using osu.Framework.Platform;
-using SQLite.Net;
-using SQLite.Net.Interop;
-using SQLite.Net.Platform.Generic;
-using SQLite.Net.Platform.Win32;
namespace osu.Game.Tests.Platform
{
@@ -16,14 +11,9 @@ namespace osu.Game.Tests.Platform
{
}
- public override SQLiteConnection GetDatabase(string name)
+ public override string GetDatabaseConnectionString(string name)
{
- ISQLitePlatform platform;
- if (RuntimeInfo.IsWindows)
- platform = new SQLitePlatformWin32();
- else
- platform = new SQLitePlatformGeneric();
- return new SQLiteConnection(platform, @":memory:");
+ return "DataSource=:memory:";
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs
index d722f7d711..ca0aaebb5e 100644
--- a/osu.Game/Tests/Visual/OsuTestCase.cs
+++ b/osu.Game/Tests/Visual/OsuTestCase.cs
@@ -11,8 +11,13 @@ namespace osu.Game.Tests.Visual
{
public override void RunTest()
{
- using (var host = new HeadlessGameHost(AppDomain.CurrentDomain.FriendlyName.Replace(' ', '-'), realtime: false))
+ using (var host = new HeadlessGameHost($"test-{Guid.NewGuid()}", realtime: false))
+ {
host.Run(new OsuTestCaseTestRunner(this));
+ }
+
+ // clean up after each run
+ //storage.DeleteDirectory(string.Empty);
}
public class OsuTestCaseTestRunner : OsuGameBase
diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs
index cd4d97425b..5306121a92 100644
--- a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs
+++ b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual
Source = "osu!lazer",
Tags = "this beatmap has all the metrics",
},
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 7,
DrainRate = 1,
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual
Source = "osu!lazer",
Tags = "this beatmap has ratings metrics but not retries or fails",
},
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 6,
DrainRate = 9,
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
Source = "osu!lazer",
Tags = "this beatmap has retries and fails but no ratings",
},
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 3.7f,
DrainRate = 6,
@@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual
Source = "osu!lazer",
Tags = "this beatmap has no metrics",
},
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs
index 72d97f905c..1ade0be626 100644
--- a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs
+++ b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs
@@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 1.36,
Version = @"BASIC",
Ruleset = mania,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 6.5f,
@@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 2.22,
Version = @"NOVICE",
Ruleset = mania,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 7,
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 3.49,
Version = @"ADVANCED",
Ruleset = mania,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 7.5f,
@@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 4.24,
Version = @"EXHAUST",
Ruleset = mania,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 8,
@@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 5.26,
Version = @"GRAVITY",
Ruleset = mania,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 4,
DrainRate = 8.5f,
@@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 1.40,
Version = @"yzrin's Kantan",
Ruleset = taiko,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 7,
@@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 2.23,
Version = @"Futsuu",
Ruleset = taiko,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 6,
@@ -295,7 +295,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 3.19,
Version = @"Muzukashii",
Ruleset = taiko,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 2,
DrainRate = 6,
@@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 3.97,
Version = @"Charlotte's Oni",
Ruleset = taiko,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 6,
@@ -351,7 +351,7 @@ namespace osu.Game.Tests.Visual
StarDifficulty = 5.08,
Version = @"Labyrinth Oni",
Ruleset = taiko,
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
diff --git a/osu.Game/Tests/Visual/TestCaseMods.cs b/osu.Game/Tests/Visual/TestCaseMods.cs
index ef250edcc3..0447d6582d 100644
--- a/osu.Game/Tests/Visual/TestCaseMods.cs
+++ b/osu.Game/Tests/Visual/TestCaseMods.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual
AddStep("Toggle", modSelect.ToggleVisibility);
- foreach (var ruleset in rulesets.AllRulesets)
+ foreach (var ruleset in rulesets.AvailableRulesets)
AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset);
}
}
diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs
index 3ea976b96f..965308c32c 100644
--- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs
+++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2007-2017 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
using osu.Framework.Allocation;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Database;
-using osu.Game.IO;
using osu.Game.Rulesets;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
@@ -24,8 +26,6 @@ namespace osu.Game.Tests.Visual
private DependencyContainer dependencies;
- private FileStore files;
-
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
@@ -37,12 +37,14 @@ namespace osu.Game.Tests.Visual
{
var storage = new TestStorage(@"TestCasePlaySongSelect");
- var backingDatabase = storage.GetDatabase(@"client");
- backingDatabase.CreateTable();
+ // this is by no means clean. should be replacing inside of OsuGameBase somehow.
+ var context = new OsuDbContext(storage.GetDatabaseConnectionString(@"client"));
+ context.Database.Migrate();
- dependencies.Cache(rulesets = new RulesetStore(backingDatabase));
- dependencies.Cache(files = new FileStore(backingDatabase, storage));
- dependencies.Cache(manager = new BeatmapManager(storage, files, backingDatabase, rulesets, null));
+ Func contextFactory = () => context;
+
+ dependencies.Cache(rulesets = new RulesetStore(contextFactory));
+ dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null));
for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i));
@@ -75,10 +77,10 @@ namespace osu.Game.Tests.Visual
new BeatmapInfo
{
OnlineBeatmapID = 1234 + i,
- Ruleset = rulesets.Query().First(),
+ Ruleset = rulesets.AvailableRulesets.First(),
Path = "normal.osu",
Version = "Normal",
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
@@ -86,10 +88,10 @@ namespace osu.Game.Tests.Visual
new BeatmapInfo
{
OnlineBeatmapID = 1235 + i,
- Ruleset = rulesets.Query().First(),
+ Ruleset = rulesets.AvailableRulesets.First(),
Path = "hard.osu",
Version = "Hard",
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 5,
}
@@ -97,10 +99,10 @@ namespace osu.Game.Tests.Visual
new BeatmapInfo
{
OnlineBeatmapID = 1236 + i,
- Ruleset = rulesets.Query().First(),
+ Ruleset = rulesets.AvailableRulesets.First(),
Path = "insane.osu",
Version = "Insane",
- Difficulty = new BeatmapDifficulty
+ BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 7,
}
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index b0953ceb7e..dfbdaf1d9d 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual
string instantiation = ruleset?.AssemblyQualifiedName;
- foreach (var r in rulesets.Query(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation)))
+ foreach (var r in rulesets.AvailableRulesets.Where(rs => instantiation == null || rs.InstantiationInfo == instantiation))
AddStep(r.Name, () => loadPlayerFor(r));
}
diff --git a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs
index d0761e5841..40fb22af1e 100644
--- a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs
+++ b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual
HitObjects = objects,
BeatmapInfo = new BeatmapInfo
{
- Difficulty = new BeatmapDifficulty(),
+ BaseDifficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata()
}
};
diff --git a/osu.Game/app.config b/osu.Game/app.config
index a704cc3750..7f2ad68041 100644
--- a/osu.Game/app.config
+++ b/osu.Game/app.config
@@ -10,6 +10,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 4b9297b963..601c99e19f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -103,6 +103,48 @@
$(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll
True
+
+ $(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll
+
+
+ $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll
+
+
+ $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Design.2.0.0\lib\net461\Microsoft.EntityFrameworkCore.Design.dll
+
+
+ $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Relational.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll
+
+
+ $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Sqlite.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Configuration.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Logging.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Logging.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll
+
+
+ $(SolutionDir)\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll
+
$(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll
@@ -116,27 +158,52 @@
$(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll
True
+
+ $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll
+
$(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll
True
-
- $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll
+
+ $(SolutionDir)\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_green.dll
True
-
- $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll
+
+