diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs index c22726491d..dd17d62739 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -26,7 +26,7 @@ namespace osu.Desktop.VisualTests.Tests if (db == null) { storage = new TestStorage(@"TestCasePlaySongSelect"); - db = new BeatmapDatabase(storage); + db = new BeatmapDatabase(storage, storage.GetDatabase(@"client")); var sets = new List(); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 221cd5a37c..6099b1e115 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -4,7 +4,6 @@ using OpenTK.Graphics; using osu.Game.Beatmaps.Timing; using osu.Game.Database; -using osu.Game.Modes; using osu.Game.Modes.Objects; using System.Collections.Generic; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 252965fee5..02595df77a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.Modes; using OpenTK; using OpenTK.Graphics; diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index 41ddd8df39..9695ba8bd0 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -18,37 +18,19 @@ using SQLiteNetExtensions.Extensions; namespace osu.Game.Database { - public class BeatmapDatabase + public class BeatmapDatabase : Database { - private SQLiteConnection connection { get; } - private readonly Storage storage; + public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private BeatmapIPCChannel ipc; - public BeatmapDatabase(Storage storage, IIpcHost importHost = null) + public BeatmapDatabase(Storage storage, SQLiteConnection connection, IIpcHost importHost = null) : base(storage, connection) { - this.storage = storage; - if (importHost != null) ipc = new BeatmapIPCChannel(importHost, this); - - if (connection == null) - { - try - { - connection = prepareConnection(); - deletePending(); - } - catch (Exception e) - { - Logger.Error(e, @"Failed to initialise the beatmap database! Trying again with a clean database..."); - storage.DeleteDatabase(@"beatmaps"); - connection = prepareConnection(); - } - } } private void deletePending() @@ -57,20 +39,20 @@ namespace osu.Game.Database { try { - storage.Delete(b.Path); + Storage.Delete(b.Path); GetChildren(b, true); foreach (var i in b.Beatmaps) { - if (i.Metadata != null) connection.Delete(i.Metadata); - if (i.Difficulty != null) connection.Delete(i.Difficulty); + if (i.Metadata != null) Connection.Delete(i.Metadata); + if (i.Difficulty != null) Connection.Delete(i.Difficulty); - connection.Delete(i); + Connection.Delete(i); } - if (b.Metadata != null) connection.Delete(b.Metadata); - connection.Delete(b); + if (b.Metadata != null) Connection.Delete(b.Metadata); + Connection.Delete(b); } catch (Exception e) { @@ -80,41 +62,31 @@ namespace osu.Game.Database //this is required because sqlite migrations don't work, initially inserting nulls into this field. //see https://github.com/praeclarum/sqlite-net/issues/326 - connection.Query("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL"); + Connection.Query("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL"); } - private SQLiteConnection prepareConnection() + protected override void Prepare() { - var conn = storage.GetDatabase(@"beatmaps"); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); - try - { - conn.CreateTable(); - conn.CreateTable(); - conn.CreateTable(); - conn.CreateTable(); - } - catch - { - conn.Close(); - throw; - } - - return conn; + deletePending(); } - public void Reset() + public override void Reset() { foreach (var setInfo in Query()) { - if (storage.Exists(setInfo.Path)) - storage.Delete(setInfo.Path); + if (Storage.Exists(setInfo.Path)) + Storage.Delete(setInfo.Path); } - connection.DeleteAll(); - connection.DeleteAll(); - connection.DeleteAll(); - connection.DeleteAll(); + Connection.DeleteAll(); + Connection.DeleteAll(); + Connection.DeleteAll(); + Connection.DeleteAll(); } /// @@ -174,7 +146,7 @@ namespace osu.Game.Database BeatmapMetadata metadata; - using (var reader = ArchiveReader.GetReader(storage, path)) + using (var reader = ArchiveReader.GetReader(Storage, path)) { using (var stream = new StreamReader(reader.GetStream(reader.BeatmapFilenames[0]))) metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; @@ -182,18 +154,18 @@ namespace osu.Game.Database if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { - using (var input = storage.GetStream(path)) + using (var input = Storage.GetStream(path)) { hash = input.GetMd5Hash(); input.Seek(0, SeekOrigin.Begin); path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); - if (!storage.Exists(path)) - using (var output = storage.GetStream(path, FileAccess.Write)) + if (!Storage.Exists(path)) + using (var output = Storage.GetStream(path, FileAccess.Write)) input.CopyTo(output); } } - var existing = connection.Table().FirstOrDefault(b => b.Hash == hash); + var existing = Connection.Table().FirstOrDefault(b => b.Hash == hash); if (existing != null) { @@ -216,7 +188,7 @@ namespace osu.Game.Database Metadata = metadata }; - using (var archive = ArchiveReader.GetReader(storage, path)) + using (var archive = ArchiveReader.GetReader(Storage, path)) { string[] mapNames = archive.BeatmapFilenames; foreach (var name in mapNames) @@ -248,17 +220,17 @@ namespace osu.Game.Database public void Import(IEnumerable beatmapSets) { - lock (connection) + lock (Connection) { - connection.BeginTransaction(); + Connection.BeginTransaction(); foreach (var s in beatmapSets) { - connection.InsertWithChildren(s, true); + Connection.InsertWithChildren(s, true); BeatmapSetAdded?.Invoke(s); } - connection.Commit(); + Connection.Commit(); } } @@ -275,7 +247,7 @@ namespace osu.Game.Database if (string.IsNullOrEmpty(beatmapSet.Path)) return null; - return ArchiveReader.GetReader(storage, beatmapSet.Path); + return ArchiveReader.GetReader(Storage, beatmapSet.Path); } public BeatmapSetInfo GetBeatmapSet(int id) @@ -305,25 +277,25 @@ namespace osu.Game.Database public TableQuery Query() where T : class { - return connection.Table(); + return Connection.Table(); } public T GetWithChildren(object id) where T : class { - return connection.GetWithChildren(id); + return Connection.GetWithChildren(id); } public List GetAllWithChildren(Expression> filter = null, bool recursive = true) where T : class { - return connection.GetAllWithChildren(filter, recursive); + return Connection.GetAllWithChildren(filter, recursive); } public T GetChildren(T item, bool recursive = false) { if (item == null) return default(T); - connection.GetChildren(item, recursive); + Connection.GetChildren(item, recursive); return item; } @@ -339,11 +311,11 @@ namespace osu.Game.Database if (validTypes.All(t => t != typeof(T))) throw new ArgumentException("Must be a type managed by BeatmapDatabase", nameof(T)); if (cascade) - connection.UpdateWithChildren(record); + Connection.UpdateWithChildren(record); else - connection.Update(record); + Connection.Update(record); } - public bool Exists(BeatmapSetInfo beatmapSet) => storage.Exists(beatmapSet.Path); + public bool Exists(BeatmapSetInfo beatmapSet) => Storage.Exists(beatmapSet.Path); } } diff --git a/osu.Game/Database/Database.cs b/osu.Game/Database/Database.cs new file mode 100644 index 0000000000..6f8e902663 --- /dev/null +++ b/osu.Game/Database/Database.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Logging; +using osu.Framework.Platform; +using SQLite.Net; + +namespace osu.Game.Database +{ + public abstract class Database + { + protected SQLiteConnection Connection { get; } + protected Storage Storage { get; } + + protected Database(Storage storage, SQLiteConnection connection) + { + Storage = storage; + Connection = connection; + + try + { + Prepare(); + } + catch (Exception e) + { + Logger.Error(e, @"Failed to initialise the beatmap database! Trying again with a clean database..."); + storage.DeleteDatabase(@"beatmaps"); + Reset(); + Prepare(); + } + } + + /// + /// Prepare this database for use. + /// + protected abstract void Prepare(); + + /// + /// Reset this database to a default state. Undo all changes to database and storage backings. + /// + public abstract void Reset(); + } +} \ No newline at end of file diff --git a/osu.Game/Database/ScoreDatabase.cs b/osu.Game/Database/ScoreDatabase.cs index 3b5c0575d5..240b4fa8e6 100644 --- a/osu.Game/Database/ScoreDatabase.cs +++ b/osu.Game/Database/ScoreDatabase.cs @@ -10,10 +10,11 @@ using osu.Game.IPC; using osu.Game.Modes; using osu.Game.Modes.Scoring; using SharpCompress.Compressors.LZMA; +using SQLite.Net; namespace osu.Game.Database { - public class ScoreDatabase + public class ScoreDatabase : Database { private readonly Storage storage; private readonly BeatmapDatabase beatmaps; @@ -23,7 +24,7 @@ namespace osu.Game.Database // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; - public ScoreDatabase(Storage storage, IIpcHost importHost = null, BeatmapDatabase beatmaps = null) + public ScoreDatabase(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapDatabase beatmaps = null) : base(storage, connection) { this.storage = storage; this.beatmaps = beatmaps; @@ -39,7 +40,7 @@ namespace osu.Game.Database using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename))) using (SerializationReader sr = new SerializationReader(s)) { - var ruleset = RulesetCollection.GetRuleset((int)sr.ReadByte()); + var ruleset = RulesetCollection.GetRuleset(sr.ReadByte()); score = ruleset.CreateScoreProcessor().CreateScore(); /* score.Pass = true;*/ @@ -107,5 +108,13 @@ namespace osu.Game.Database return score; } + + protected override void Prepare() + { + } + + public override void Reset() + { + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index f95e8c3ac6..c37d2b83eb 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; using osu.Game.Online.API; +using SQLite.Net; namespace osu.Game { @@ -80,8 +81,11 @@ namespace osu.Game { Dependencies.Cache(this); Dependencies.Cache(LocalConfig); - Dependencies.Cache(BeatmapDatabase = new BeatmapDatabase(Host.Storage, Host)); - Dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, Host, BeatmapDatabase)); + + SQLiteConnection connection = Host.Storage.GetDatabase(@"client"); + + Dependencies.Cache(BeatmapDatabase = new BeatmapDatabase(Host.Storage, connection, Host)); + Dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, connection, Host, BeatmapDatabase)); Dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 866d3ddacb..a450125e48 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -78,6 +78,7 @@ +