1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:07:52 +08:00

*Database -> *Store

Welcome back BeatmapManager
This commit is contained in:
Dean Herbert 2017-07-27 16:56:41 +09:00
parent fdc6666c71
commit 5f53426a9a
36 changed files with 570 additions and 580 deletions

View File

@ -15,7 +15,7 @@ namespace osu.Desktop.VisualTests.Tests
public override string Description => @"osu!direct overlay";
private DirectOverlay direct;
private RulesetDatabase rulesets;
private RulesetStore rulesets;
protected override void LoadComplete()
{
@ -29,7 +29,7 @@ namespace osu.Desktop.VisualTests.Tests
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -17,7 +17,7 @@ namespace osu.Desktop.VisualTests.Tests
{
public override string Description => @"Select your favourite room";
private RulesetDatabase rulesets;
private RulesetStore rulesets;
protected override void LoadComplete()
{
@ -125,7 +125,7 @@ namespace osu.Desktop.VisualTests.Tests
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -24,12 +24,12 @@ namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseGamefield : TestCase
{
private RulesetDatabase rulesets;
private RulesetStore rulesets;
public override string Description => @"Showing hitobjects and what not.";
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -18,11 +18,11 @@ namespace osu.Desktop.VisualTests.Tests
private ModSelectOverlay modSelect;
private ModDisplay modDisplay;
private RulesetDatabase rulesets;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -14,27 +14,27 @@ namespace osu.Desktop.VisualTests.Tests
{
internal class TestCasePlaySongSelect : TestCase
{
private readonly BeatmapStore store;
private readonly BeatmapManager manager;
public override string Description => @"with fake data";
private readonly RulesetDatabase rulesets;
private readonly RulesetStore rulesets;
public TestCasePlaySongSelect()
{
PlaySongSelect songSelect;
if (store == null)
if (manager == null)
{
var storage = new TestStorage(@"TestCasePlaySongSelect");
var backingDatabase = storage.GetDatabase(@"client");
rulesets = new RulesetDatabase(backingDatabase);
store = new BeatmapStore(storage, null, backingDatabase, rulesets);
rulesets = new RulesetStore(backingDatabase);
manager = new BeatmapManager(storage, null, backingDatabase, rulesets);
for (int i = 0; i < 100; i += 10)
store.Import(createTestBeatmapSet(i));
manager.Import(createTestBeatmapSet(i));
}
Add(songSelect = new PlaySongSelect());

View File

@ -20,12 +20,12 @@ namespace osu.Desktop.VisualTests.Tests
internal class TestCasePlayer : TestCase
{
protected Player Player;
private RulesetDatabase rulesets;
private RulesetStore rulesets;
public override string Description => @"Showing everything to play the game.";
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -14,12 +14,12 @@ namespace osu.Desktop.VisualTests.Tests
{
internal class TestCaseResults : TestCase
{
private BeatmapStore beatmaps;
private BeatmapManager beatmaps;
public override string Description => @"Results after playing.";
[BackgroundDependencyLoader]
private void load(BeatmapStore beatmaps)
private void load(BeatmapManager beatmaps)
{
this.beatmaps = beatmaps;
}

View File

@ -16,7 +16,7 @@ namespace osu.Desktop.VisualTests.Tests
{
public override string Description => @"from the multiplayer lobby";
private RulesetDatabase rulesets;
private RulesetStore rulesets;
protected override void LoadComplete()
{
@ -136,7 +136,7 @@ namespace osu.Desktop.VisualTests.Tests
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}

View File

@ -65,11 +65,11 @@ namespace osu.Desktop
var filePaths = dropData.Select(f => f.ToString()).ToArray();
if (filePaths.All(f => Path.GetExtension(f) == @".osz"))
Task.Run(() => BeatmapStore.Import(filePaths));
Task.Run(() => BeatmapManager.Import(filePaths));
else if (filePaths.All(f => Path.GetExtension(f) == @".osr"))
Task.Run(() =>
{
var score = ScoreDatabase.ReadReplayFile(filePaths.First());
var score = ScoreStore.ReadReplayFile(filePaths.First());
Schedule(() => LoadScore(score));
});
}

View File

@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(File.Exists(temp));
osu.Dependencies.Get<BeatmapStore>().Import(temp);
osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
using (File.OpenRead(temp))
osu.Dependencies.Get<BeatmapStore>().Import(temp);
osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
@ -105,8 +105,8 @@ namespace osu.Game.Tests.Beatmaps.IO
Thread.Sleep(1);
//reset beatmap database (sqlite and storage backing)
osu.Dependencies.Get<RulesetDatabase>().Reset();
osu.Dependencies.Get<BeatmapStore>().Reset();
osu.Dependencies.Get<RulesetStore>().Reset();
osu.Dependencies.Get<BeatmapManager>().Reset();
return osu;
}
@ -115,7 +115,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
IEnumerable<BeatmapSetInfo> resultSets = null;
var store = osu.Dependencies.Get<BeatmapStore>();
var store = osu.Dependencies.Get<BeatmapManager>();
Action waitAction = () =>
{

View File

@ -1,116 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Logging;
using osu.Game.Database;
using SQLite.Net;
using SQLiteNetExtensions.Extensions;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
/// </summary>
public class BeatmapDatabase : DatabaseStore
{
public event Action<BeatmapSetInfo> BeatmapSetAdded;
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
public BeatmapDatabase(SQLiteConnection connection)
: base(connection)
{
}
protected override Type[] ValidTypes => new[]
{
typeof(BeatmapSetInfo),
typeof(BeatmapInfo),
typeof(BeatmapMetadata),
typeof(BeatmapDifficulty),
};
protected override void Prepare(bool reset = false)
{
if (reset)
{
Connection.DropTable<BeatmapMetadata>();
Connection.DropTable<BeatmapDifficulty>();
Connection.DropTable<BeatmapSetInfo>();
Connection.DropTable<BeatmapSetFileInfo>();
Connection.DropTable<BeatmapInfo>();
}
Connection.CreateTable<BeatmapMetadata>();
Connection.CreateTable<BeatmapDifficulty>();
Connection.CreateTable<BeatmapSetInfo>();
Connection.CreateTable<BeatmapSetFileInfo>();
Connection.CreateTable<BeatmapInfo>();
cleanupPendingDeletions();
}
/// <summary>
/// Add a <see cref="BeatmapSetInfo"/> to the database.
/// </summary>
/// <param name="beatmapSet">The beatmap to add.</param>
public void Add(BeatmapSetInfo beatmapSet)
{
Connection.InsertOrReplaceWithChildren(beatmapSet, true);
BeatmapSetAdded?.Invoke(beatmapSet);
}
/// <summary>
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Delete(BeatmapSetInfo beatmapSet)
{
if (beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = true;
Connection.Update(beatmapSet);
BeatmapSetRemoved?.Invoke(beatmapSet);
return true;
}
/// <summary>
/// Restore a previously deleted <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSet">The beatmap to restore.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Undelete(BeatmapSetInfo beatmapSet)
{
if (!beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = false;
Connection.Update(beatmapSet);
BeatmapSetAdded?.Invoke(beatmapSet);
return true;
}
private void cleanupPendingDeletions()
{
foreach (var b in QueryAndPopulate<BeatmapSetInfo>(b => b.DeletePending && !b.Protected))
{
try
{
// many-to-many join table entries are not automatically tidied.
Connection.Table<BeatmapSetFileInfo>().Delete(f => f.BeatmapSetInfoID == b.ID);
Connection.Delete(b, true);
}
catch (Exception e)
{
Logger.Error(e, $@"Could not delete beatmap {b}");
}
}
//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<BeatmapSetInfo>("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL");
}
}
}

View File

@ -0,0 +1,394 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using Ionic.Zip;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
using osu.Game.IO;
using osu.Game.IPC;
using osu.Game.Rulesets;
using SQLite.Net;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
/// </summary>
public class BeatmapManager
{
/// <summary>
/// Fired when a new <see cref="BeatmapSetInfo"/> becomes available in the database.
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetAdded;
/// <summary>
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
public WorkingBeatmap DefaultBeatmap { private get; set; }
private readonly Storage storage;
private readonly FileStore files;
private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps;
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private BeatmapIPCChannel ipc;
public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, IIpcHost importHost = null)
{
beatmaps = new BeatmapStore(connection);
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
this.storage = storage;
this.files = files;
this.rulesets = rulesets;
if (importHost != null)
ipc = new BeatmapIPCChannel(importHost, this);
}
/// <summary>
/// Import multiple <see cref="BeatmapSetInfo"/> from filesystem <paramref name="paths"/>.
/// </summary>
/// <param name="paths">Multiple locations on disk.</param>
public void Import(params string[] paths)
{
foreach (string path in paths)
{
try
{
using (ArchiveReader reader = getReaderFrom(path))
Import(reader);
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with beatmaps from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted.
try
{
File.Delete(path);
}
catch (Exception e)
{
Logger.Error(e, $@"Could not delete file at {path}");
}
}
catch (Exception e)
{
e = e.InnerException ?? e;
Logger.Error(e, @"Could not import beatmap set");
}
}
}
/// <summary>
/// Import a beatmap from an <see cref="ArchiveReader"/>.
/// </summary>
/// <param name="archiveReader">The beatmap to be imported.</param>
public BeatmapSetInfo Import(ArchiveReader archiveReader)
{
BeatmapSetInfo set = importToStorage(archiveReader);
Import(set);
return set;
}
/// <summary>
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSetInfo">The beatmap to be imported.</param>
public void Import(BeatmapSetInfo beatmapSetInfo)
{
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
beatmaps.Add(beatmapSetInfo);
}
/// <summary>
/// Delete a beatmap from the manager.
/// Is a no-op for already deleted beatmaps.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files);
}
/// <summary>
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
/// Is a no-op for already usable beatmaps.
/// </summary>
/// <param name="beatmapSet">The beatmap to restore.</param>
public void Undelete(BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Undelete(beatmapSet)) return;
files.Reference(beatmapSet.Files);
}
/// <summary>
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
/// </summary>
/// <param name="beatmapInfo">The beatmap to lookup.</param>
/// <param name="previous">The currently loaded <see cref="WorkingBeatmap"/>. Allows for optimisation where elements are shared with the new beatmap.</param>
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null)
{
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
beatmaps.Populate(beatmapInfo);
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(files.Store, beatmapInfo);
previous?.TransferTo(working);
return working;
}
/// <summary>
/// Reset the manager to an empty state.
/// </summary>
public void Reset()
{
beatmaps.Reset();
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapSetInfo QueryBeatmapSet(Func<BeatmapSetInfo, bool> query)
{
BeatmapSetInfo set = beatmaps.Query<BeatmapSetInfo>().FirstOrDefault(query);
if (set != null)
beatmaps.Populate(set);
return set;
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns>
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.QueryAndPopulate(query);
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
{
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
if (set != null)
beatmaps.Populate(set);
return set;
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns>
public List<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.QueryAndPopulate(query);
/// <summary>
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
/// </summary>
/// <param name="path">A file or folder path resolving the beatmap content.</param>
/// <returns>A reader giving access to the beatmap's content.</returns>
private ArchiveReader getReaderFrom(string path)
{
if (ZipFile.IsZipFile(path))
return new OszArchiveReader(storage.GetStream(path));
else
return new LegacyFilesystemReader(path);
}
/// <summary>
/// Import a beamap into our local <see cref="FileStore"/> storage.
/// If the beatmap is already imported, the existing instance will be returned.
/// </summary>
/// <param name="reader">The beatmap archive to be read.</param>
/// <returns>The imported beatmap, or an existing instance if it is already present.</returns>
private BeatmapSetInfo importToStorage(ArchiveReader reader)
{
// for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu")))
using (Stream s = reader.GetStream(file))
s.CopyTo(hashable);
var hash = hashable.GetMd5Hash();
// check if this beatmap has already been imported and exit early if so.
var beatmapSet = beatmaps.QueryAndPopulate<BeatmapSetInfo>().FirstOrDefault(b => b.Hash == hash);
if (beatmapSet != null)
{
Undelete(beatmapSet);
return beatmapSet;
}
List<FileInfo> fileInfos = new List<FileInfo>();
// import files to manager
foreach (string file in reader.Filenames)
using (Stream s = reader.GetStream(file))
fileInfos.Add(files.Add(s, file));
BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))))
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
beatmapSet = new BeatmapSetInfo
{
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(),
Hash = hash,
Files = fileInfos,
Metadata = metadata
};
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
foreach (var name in mapNames)
{
using (var raw = reader.GetStream(name))
using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
using (var sr = new StreamReader(ms))
{
raw.CopyTo(ms);
ms.Position = 0;
var decoder = BeatmapDecoder.GetDecoder(sr);
Beatmap beatmap = decoder.Decode(sr);
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.GetMd5Hash();
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null;
// TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.Ruleset = rulesets.Query<RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID);
beatmap.BeatmapInfo.StarDifficulty = rulesets.Query<RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap)
.Calculate() ?? 0;
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
}
}
return beatmapSet;
}
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="populate">Whether returned objects should be pre-populated with all data.</param>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public List<BeatmapSetInfo> GetAllUsableBeatmapSets(bool populate = true)
{
if (populate)
return beatmaps.QueryAndPopulate<BeatmapSetInfo>(b => !b.DeletePending).ToList();
else
return beatmaps.Query<BeatmapSetInfo>(b => !b.DeletePending).ToList();
}
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{
private readonly IResourceStore<byte[]> store;
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo)
: base(beatmapInfo)
{
this.store = store;
}
protected override Beatmap GetBeatmap()
{
try
{
Beatmap beatmap;
BeatmapDecoder decoder;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
{
decoder = BeatmapDecoder.GetDecoder(stream);
beatmap = decoder.Decode(stream);
}
if (beatmap == null || BeatmapSetInfo.StoryboardFile == null)
return beatmap;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
decoder.Decode(stream, beatmap);
return beatmap;
}
catch { return null; }
}
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).StoragePath;
protected override Texture GetBackground()
{
if (Metadata?.BackgroundFile == null)
return null;
try
{
return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile));
}
catch { return null; }
}
protected override Track GetTrack()
{
try
{
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new TrackBass(trackData);
}
catch { return new TrackVirtual(); }
}
}
}
}

View File

@ -1,331 +1,116 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using Ionic.Zip;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.IO;
using osu.Game.IO;
using osu.Game.IPC;
using osu.Game.Rulesets;
using osu.Game.Database;
using SQLite.Net;
using FileInfo = osu.Game.IO.FileInfo;
using SQLiteNetExtensions.Extensions;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
/// </summary>
public class BeatmapStore
public class BeatmapStore : DatabaseBackedStore
{
/// <summary>
/// Fired when a new <see cref="BeatmapSetInfo"/> becomes available in the database.
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetAdded;
/// <summary>
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
public WorkingBeatmap DefaultBeatmap { private get; set; }
private readonly Storage storage;
private readonly FileDatabase files;
private readonly RulesetDatabase rulesets;
private readonly BeatmapDatabase beatmaps;
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private BeatmapIPCChannel ipc;
public BeatmapStore(Storage storage, FileDatabase files, SQLiteConnection connection, RulesetDatabase rulesets, IIpcHost importHost = null)
public BeatmapStore(SQLiteConnection connection)
: base(connection)
{
beatmaps = new BeatmapDatabase(connection);
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
}
this.storage = storage;
this.files = files;
this.rulesets = rulesets;
protected override Type[] ValidTypes => new[]
{
typeof(BeatmapSetInfo),
typeof(BeatmapInfo),
typeof(BeatmapMetadata),
typeof(BeatmapDifficulty),
};
if (importHost != null)
ipc = new BeatmapIPCChannel(importHost, this);
protected override void Prepare(bool reset = false)
{
if (reset)
{
Connection.DropTable<BeatmapMetadata>();
Connection.DropTable<BeatmapDifficulty>();
Connection.DropTable<BeatmapSetInfo>();
Connection.DropTable<BeatmapSetFileInfo>();
Connection.DropTable<BeatmapInfo>();
}
Connection.CreateTable<BeatmapMetadata>();
Connection.CreateTable<BeatmapDifficulty>();
Connection.CreateTable<BeatmapSetInfo>();
Connection.CreateTable<BeatmapSetFileInfo>();
Connection.CreateTable<BeatmapInfo>();
cleanupPendingDeletions();
}
/// <summary>
/// Import multiple <see cref="BeatmapSetInfo"/> from filesystem <paramref name="paths"/>.
/// Add a <see cref="BeatmapSetInfo"/> to the database.
/// </summary>
/// <param name="paths">Multiple locations on disk.</param>
public void Import(params string[] paths)
/// <param name="beatmapSet">The beatmap to add.</param>
public void Add(BeatmapSetInfo beatmapSet)
{
foreach (string path in paths)
Connection.InsertOrReplaceWithChildren(beatmapSet, true);
BeatmapSetAdded?.Invoke(beatmapSet);
}
/// <summary>
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Delete(BeatmapSetInfo beatmapSet)
{
if (beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = true;
Connection.Update(beatmapSet);
BeatmapSetRemoved?.Invoke(beatmapSet);
return true;
}
/// <summary>
/// Restore a previously deleted <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSet">The beatmap to restore.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
public bool Undelete(BeatmapSetInfo beatmapSet)
{
if (!beatmapSet.DeletePending) return false;
beatmapSet.DeletePending = false;
Connection.Update(beatmapSet);
BeatmapSetAdded?.Invoke(beatmapSet);
return true;
}
private void cleanupPendingDeletions()
{
foreach (var b in QueryAndPopulate<BeatmapSetInfo>(b => b.DeletePending && !b.Protected))
{
try
{
using (ArchiveReader reader = getReaderFrom(path))
Import(reader);
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with beatmaps from default storage.
// Also, not always a single file, i.e. for LegacyFilesystemReader
// TODO: Add a check to prevent files from storage to be deleted.
try
{
File.Delete(path);
}
catch (Exception e)
{
Logger.Error(e, $@"Could not delete file at {path}");
}
// many-to-many join table entries are not automatically tidied.
Connection.Table<BeatmapSetFileInfo>().Delete(f => f.BeatmapSetInfoID == b.ID);
Connection.Delete(b, true);
}
catch (Exception e)
{
e = e.InnerException ?? e;
Logger.Error(e, @"Could not import beatmap set");
}
}
}
/// <summary>
/// Import a beatmap from an <see cref="ArchiveReader"/>.
/// </summary>
/// <param name="archiveReader">The beatmap to be imported.</param>
public BeatmapSetInfo Import(ArchiveReader archiveReader)
{
BeatmapSetInfo set = importToStorage(archiveReader);
Import(set);
return set;
}
/// <summary>
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSetInfo">The beatmap to be imported.</param>
public void Import(BeatmapSetInfo beatmapSetInfo)
{
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
beatmaps.Add(beatmapSetInfo);
}
/// <summary>
/// Delete a beatmap from the store.
/// Is a no-op for already deleted beatmaps.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files);
}
/// <summary>
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
/// Is a no-op for already usable beatmaps.
/// </summary>
/// <param name="beatmapSet">The beatmap to restore.</param>
public void Undelete(BeatmapSetInfo beatmapSet)
{
if (!beatmaps.Undelete(beatmapSet)) return;
files.Reference(beatmapSet.Files);
}
/// <summary>
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
/// </summary>
/// <param name="beatmapInfo">The beatmap to lookup.</param>
/// <param name="previous">The currently loaded <see cref="WorkingBeatmap"/>. Allows for optimisation where elements are shared with the new beatmap.</param>
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null)
{
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap;
beatmaps.Populate(beatmapInfo);
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapStoreWorkingBeatmap(files.Store, beatmapInfo);
previous?.TransferTo(working);
return working;
}
/// <summary>
/// Reset the store to an empty state.
/// </summary>
public void Reset()
{
beatmaps.Reset();
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapSetInfo QueryBeatmapSet(Func<BeatmapSetInfo, bool> query)
{
BeatmapSetInfo set = beatmaps.Query<BeatmapSetInfo>().FirstOrDefault(query);
if (set != null)
beatmaps.Populate(set);
return set;
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns>
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query) => beatmaps.QueryAndPopulate(query);
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
{
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
if (set != null)
beatmaps.Populate(set);
return set;
}
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>Results from the provided query.</returns>
public List<BeatmapInfo> QueryBeatmaps(Expression<Func<BeatmapInfo, bool>> query) => beatmaps.QueryAndPopulate(query);
/// <summary>
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
/// </summary>
/// <param name="path">A file or folder path resolving the beatmap content.</param>
/// <returns>A reader giving access to the beatmap's content.</returns>
private ArchiveReader getReaderFrom(string path)
{
if (ZipFile.IsZipFile(path))
return new OszArchiveReader(storage.GetStream(path));
else
return new LegacyFilesystemReader(path);
}
/// <summary>
/// Import a beamap into our local <see cref="FileDatabase"/> storage.
/// If the beatmap is already imported, the existing instance will be returned.
/// </summary>
/// <param name="reader">The beatmap archive to be read.</param>
/// <returns>The imported beatmap, or an existing instance if it is already present.</returns>
private BeatmapSetInfo importToStorage(ArchiveReader reader)
{
// for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu")))
using (Stream s = reader.GetStream(file))
s.CopyTo(hashable);
var hash = hashable.GetMd5Hash();
// check if this beatmap has already been imported and exit early if so.
var beatmapSet = beatmaps.QueryAndPopulate<BeatmapSetInfo>().FirstOrDefault(b => b.Hash == hash);
if (beatmapSet != null)
{
Undelete(beatmapSet);
return beatmapSet;
}
List<FileInfo> fileInfos = new List<FileInfo>();
// import files to store
foreach (string file in reader.Filenames)
using (Stream s = reader.GetStream(file))
fileInfos.Add(files.Add(s, file));
BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))))
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata;
beatmapSet = new BeatmapSetInfo
{
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(),
Hash = hash,
Files = fileInfos,
Metadata = metadata
};
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
foreach (var name in mapNames)
{
using (var raw = reader.GetStream(name))
using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
using (var sr = new StreamReader(ms))
{
raw.CopyTo(ms);
ms.Position = 0;
var decoder = BeatmapDecoder.GetDecoder(sr);
Beatmap beatmap = decoder.Decode(sr);
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.GetMd5Hash();
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null;
// TODO: this should be done in a better place once we actually need to dynamically update it.
beatmap.BeatmapInfo.Ruleset = rulesets.Query<RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID);
beatmap.BeatmapInfo.StarDifficulty = rulesets.Query<RulesetInfo>().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap)
.Calculate() ?? 0;
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
Logger.Error(e, $@"Could not delete beatmap {b}");
}
}
return beatmapSet;
}
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
/// </summary>
/// <param name="populate">Whether returned objects should be pre-populated with all data.</param>
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
public List<BeatmapSetInfo> GetAllUsableBeatmapSets(bool populate = true)
{
if (populate)
return beatmaps.QueryAndPopulate<BeatmapSetInfo>(b => !b.DeletePending).ToList();
else
return beatmaps.Query<BeatmapSetInfo>(b => !b.DeletePending).ToList();
//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<BeatmapSetInfo>("UPDATE BeatmapSetInfo SET DeletePending = 0 WHERE DeletePending IS NULL");
}
}
}

View File

@ -1,72 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps.Formats;
namespace osu.Game.Beatmaps
{
internal class BeatmapStoreWorkingBeatmap : WorkingBeatmap
{
private readonly IResourceStore<byte[]> store;
public BeatmapStoreWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo)
: base(beatmapInfo)
{
this.store = store;
}
protected override Beatmap GetBeatmap()
{
try
{
Beatmap beatmap;
BeatmapDecoder decoder;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
{
decoder = BeatmapDecoder.GetDecoder(stream);
beatmap = decoder.Decode(stream);
}
if (beatmap == null || BeatmapSetInfo.StoryboardFile == null)
return beatmap;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
decoder.Decode(stream, beatmap);
return beatmap;
}
catch { return null; }
}
private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => f.Filename == filename).StoragePath;
protected override Texture GetBackground()
{
if (Metadata?.BackgroundFile == null)
return null;
try
{
return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile));
}
catch { return null; }
}
protected override Track GetTrack()
{
try
{
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
return trackData == null ? null : new TrackBass(trackData);
}
catch { return new TrackVirtual(); }
}
}
}

View File

@ -58,10 +58,10 @@ namespace osu.Game.Beatmaps.Drawables
}
}
public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapStore store)
public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager)
{
BeatmapSet = beatmapSet;
WorkingBeatmap beatmap = store.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault());
WorkingBeatmap beatmap = manager.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault());
Header = new BeatmapSetHeader(beatmap)
{

View File

@ -12,12 +12,12 @@ using SQLiteNetExtensions.Extensions;
namespace osu.Game.Database
{
public abstract class DatabaseStore
public abstract class DatabaseBackedStore
{
protected readonly Storage Storage;
protected readonly SQLiteConnection Connection;
protected DatabaseStore(SQLiteConnection connection, Storage storage = null)
protected DatabaseBackedStore(SQLiteConnection connection, Storage storage = null)
{
Storage = storage;
Connection = connection;
@ -84,7 +84,7 @@ namespace osu.Game.Database
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(DatabaseStore)}.");
throw new InvalidOperationException($"The requested operation specified a type of {type}, which is invalid for this {nameof(DatabaseBackedStore)}.");
}
protected abstract Type[] ValidTypes { get; }

View File

@ -17,13 +17,13 @@ namespace osu.Game.IO
/// <summary>
/// Handles the Store and retrieval of Files/FileSets to the database backing
/// </summary>
public class FileDatabase : DatabaseStore
public class FileStore : DatabaseBackedStore
{
private const string prefix = "files";
public readonly ResourceStore<byte[]> Store;
public FileDatabase(SQLiteConnection connection, Storage storage) : base(connection, storage)
public FileStore(SQLiteConnection connection, Storage storage) : base(connection, storage)
{
Store = new NamespacedResourceStore<byte[]>(new StorageBackedResourceStore(storage), prefix);
}

View File

@ -10,9 +10,9 @@ namespace osu.Game.IPC
{
public class BeatmapIPCChannel : IpcChannel<BeatmapImportMessage>
{
private readonly BeatmapStore beatmaps;
private readonly BeatmapManager beatmaps;
public BeatmapIPCChannel(IIpcHost host, BeatmapStore beatmaps = null)
public BeatmapIPCChannel(IIpcHost host, BeatmapManager beatmaps = null)
: base(host)
{
this.beatmaps = beatmaps;

View File

@ -10,9 +10,9 @@ namespace osu.Game.IPC
{
public class ScoreIPCChannel : IpcChannel<ScoreImportMessage>
{
private readonly ScoreDatabase scores;
private readonly ScoreStore scores;
public ScoreIPCChannel(IIpcHost host, ScoreDatabase scores = null)
public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null)
: base(host)
{
this.scores = scores;

View File

@ -49,7 +49,7 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"beatmaps")]
private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; }
public BeatmapSetInfo ToBeatmapSet(RulesetDatabase rulesets)
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
return new BeatmapSetInfo
{
@ -79,7 +79,7 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"difficulty_rating")]
private double starDifficulty { get; set; }
public BeatmapInfo ToBeatmap(RulesetDatabase rulesets)
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
{
return new BeatmapInfo
{

View File

@ -99,13 +99,13 @@ namespace osu.Game
if (args?.Length > 0)
{
var paths = args.Where(a => !a.StartsWith(@"-"));
Task.Run(() => BeatmapStore.Import(paths.ToArray()));
Task.Run(() => BeatmapManager.Import(paths.ToArray()));
}
dependencies.Cache(this);
configRuleset = LocalConfig.GetBindable<int>(OsuSetting.Ruleset);
Ruleset.Value = RulesetDatabase.GetRuleset(configRuleset.Value);
Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value);
Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0;
}
@ -140,7 +140,7 @@ namespace osu.Game
return;
}
Beatmap.Value = BeatmapStore.GetWorkingBeatmap(s.Beatmap);
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(s.Beatmap);
menu.Push(new PlayerLoader(new ReplayPlayer(s.Replay)));
}

View File

@ -28,13 +28,13 @@ namespace osu.Game
{
protected OsuConfigManager LocalConfig;
protected BeatmapStore BeatmapStore;
protected BeatmapManager BeatmapManager;
protected RulesetDatabase RulesetDatabase;
protected RulesetStore RulesetStore;
protected FileDatabase FileDatabase;
protected FileStore FileStore;
protected ScoreDatabase ScoreDatabase;
protected ScoreStore ScoreStore;
protected override string MainResourceFile => @"osu.Game.Resources.dll";
@ -97,10 +97,10 @@ namespace osu.Game
SQLiteConnection connection = Host.Storage.GetDatabase(@"client");
dependencies.Cache(RulesetDatabase = new RulesetDatabase(connection));
dependencies.Cache(FileDatabase = new FileDatabase(connection, Host.Storage));
dependencies.Cache(BeatmapStore = new BeatmapStore(Host.Storage, FileDatabase, connection, RulesetDatabase, Host));
dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, connection, Host, BeatmapStore));
dependencies.Cache(RulesetStore = new RulesetStore(connection));
dependencies.Cache(FileStore = new FileStore(connection, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager));
dependencies.Cache(new OsuColour());
//this completely overrides the framework default. will need to change once we make a proper FontStore.
@ -132,7 +132,7 @@ namespace osu.Game
var defaultBeatmap = new DummyWorkingBeatmap(this);
Beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap);
BeatmapStore.DefaultBeatmap = defaultBeatmap;
BeatmapManager.DefaultBeatmap = defaultBeatmap;
dependencies.Cache(API = new APIAccess
{

View File

@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Direct
}
[BackgroundDependencyLoader(true)]
private void load(OsuGame game, RulesetDatabase rulesets, OsuColour colours)
private void load(OsuGame game, RulesetStore rulesets, OsuColour colours)
{
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Overlays
private const float panel_padding = 10f;
private APIAccess api;
private RulesetDatabase rulesets;
private RulesetStore rulesets;
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
@ -161,7 +161,7 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, APIAccess api, RulesetDatabase rulesets)
private void load(OsuColour colours, APIAccess api, RulesetStore rulesets)
{
this.api = api;
this.rulesets = rulesets;

View File

@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, OsuGame osu, RulesetDatabase rulesets)
private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets)
{
lowMultiplierColour = colours.Red;
highMultiplierColour = colours.Green;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Music
private FilterControl filter;
private PlaylistList list;
private BeatmapStore beatmaps;
private BeatmapManager beatmaps;
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Music
private InputManager inputManager;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapStore beatmaps, OsuColour colours, UserInputManager inputManager)
private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, UserInputManager inputManager)
{
this.inputManager = inputManager;
this.beatmaps = beatmaps;

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets)
private void load(RulesetStore rulesets)
{
foreach(Ruleset ruleset in rulesets.AllRulesets.Select(info => info.CreateInstance()))
{

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings
public class SettingsFooter : FillFlowContainer
{
[BackgroundDependencyLoader]
private void load(OsuGameBase game, OsuColour colours, RulesetDatabase rulesets)
private void load(OsuGameBase game, OsuColour colours, RulesetStore rulesets)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;

View File

@ -65,7 +65,7 @@ namespace osu.Game.Overlays.Toolbar
}
[BackgroundDependencyLoader]
private void load(RulesetDatabase rulesets, OsuGame game)
private void load(RulesetStore rulesets, OsuGame game)
{
foreach (var r in rulesets.AllRulesets)
{

View File

@ -12,13 +12,13 @@ using SQLite.Net;
namespace osu.Game.Rulesets
{
/// <summary>
/// Todo: All of this needs to be moved to a RulesetDatabase.
/// Todo: All of this needs to be moved to a RulesetStore.
/// </summary>
public class RulesetDatabase : DatabaseStore
public class RulesetStore : DatabaseBackedStore
{
public IEnumerable<RulesetInfo> AllRulesets => Query<RulesetInfo>().Where(r => r.Available);
public RulesetDatabase(SQLiteConnection connection) : base(connection)
public RulesetStore(SQLiteConnection connection) : base(connection)
{
}

View File

@ -15,19 +15,19 @@ using SQLite.Net;
namespace osu.Game.Rulesets.Scoring
{
public class ScoreDatabase : DatabaseStore
public class ScoreStore : DatabaseBackedStore
{
private readonly Storage storage;
private readonly BeatmapStore beatmaps;
private readonly RulesetDatabase rulesets;
private readonly BeatmapManager beatmaps;
private readonly RulesetStore rulesets;
private const string replay_folder = @"replays";
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ScoreIPCChannel ipc;
public ScoreDatabase(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapStore beatmaps = null, RulesetDatabase rulesets = null) : base(connection)
public ScoreStore(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(connection)
{
this.storage = storage;
this.beatmaps = beatmaps;

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu
private Track track;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config, BeatmapStore beatmaps, Framework.Game game)
private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
{
menuVoice = config.GetBindable<bool>(OsuSetting.MenuVoice);
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);

View File

@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select
/// <summary>
/// Required for now unfortunately.
/// </summary>
private BeatmapStore store;
private BeatmapManager manager;
private readonly Container<Panel> scrollableContent;
@ -289,7 +289,7 @@ namespace osu.Game.Screens.Select
b.Metadata = beatmapSet.Metadata;
}
return new BeatmapGroup(beatmapSet, store)
return new BeatmapGroup(beatmapSet, manager)
{
SelectionChanged = (g, p) => selectGroup(g, p),
StartRequested = b => StartRequested?.Invoke(),
@ -298,9 +298,9 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapStore store, OsuConfigManager config)
private void load(BeatmapManager manager, OsuConfigManager config)
{
this.store = store;
this.manager = manager;
randomType = config.GetBindable<SelectionRandomType>(OsuSetting.SelectionRandomType);
}

View File

@ -11,12 +11,12 @@ namespace osu.Game.Screens.Select
{
public class BeatmapDeleteDialog : PopupDialog
{
private BeatmapStore store;
private BeatmapManager manager;
[BackgroundDependencyLoader]
private void load(BeatmapStore beatmapStore)
private void load(BeatmapManager beatmapManager)
{
store = beatmapStore;
manager = beatmapManager;
}
public BeatmapDeleteDialog(WorkingBeatmap beatmap)
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select
Action = () =>
{
beatmap.Dispose();
store.Delete(beatmap.BeatmapSetInfo);
manager.Delete(beatmap.BeatmapSetInfo);
},
},
new PopupDialogCancelButton

View File

@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select
public abstract class SongSelect : OsuScreen
{
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private BeatmapStore store;
private BeatmapManager manager;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap();
private readonly BeatmapCarousel carousel;
@ -154,7 +154,7 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapStore beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours, UserInputManager input)
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours, UserInputManager input)
{
if (Footer != null)
{
@ -164,14 +164,14 @@ namespace osu.Game.Screens.Select
BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, promptDelete, Key.Number4, float.MaxValue);
}
if (store == null)
store = beatmaps;
if (manager == null)
manager = beatmaps;
if (osu != null)
ruleset.BindTo(osu.Ruleset);
store.BeatmapSetAdded += onBeatmapSetAdded;
store.BeatmapSetRemoved += onBeatmapSetRemoved;
manager.BeatmapSetAdded += onBeatmapSetAdded;
manager.BeatmapSetRemoved += onBeatmapSetRemoved;
dialogOverlay = dialog;
@ -180,7 +180,7 @@ namespace osu.Game.Screens.Select
initialAddSetsTask = new CancellationTokenSource();
carousel.Beatmaps = store.GetAllUsableBeatmapSets();
carousel.Beatmaps = manager.GetAllUsableBeatmapSets();
Beatmap.ValueChanged += beatmap_ValueChanged;
@ -230,7 +230,7 @@ namespace osu.Game.Screens.Select
{
bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value.BeatmapInfo.BeatmapSetInfoID;
Beatmap.Value = store.GetWorkingBeatmap(beatmap, Beatmap);
Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap);
ensurePlayingSelected(preview);
}
@ -341,10 +341,10 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
if (store != null)
if (manager != null)
{
store.BeatmapSetAdded -= onBeatmapSetAdded;
store.BeatmapSetRemoved -= onBeatmapSetRemoved;
manager.BeatmapSetAdded -= onBeatmapSetAdded;
manager.BeatmapSetRemoved -= onBeatmapSetRemoved;
}
initialAddSetsTask?.Cancel();

View File

@ -74,7 +74,7 @@
<ItemGroup>
<Compile Include="Audio\SampleInfo.cs" />
<Compile Include="Audio\SampleInfoList.cs" />
<Compile Include="Beatmaps\BeatmapDatabase.cs" />
<Compile Include="Beatmaps\BeatmapStore.cs" />
<Compile Include="Beatmaps\BeatmapSetFileInfo.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
@ -91,7 +91,7 @@
<Compile Include="Graphics\UserInterface\MenuItemType.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenu.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenuItem.cs" />
<Compile Include="IO\FileDatabase.cs" />
<Compile Include="IO\FileStore.cs" />
<Compile Include="IO\FileInfo.cs" />
<Compile Include="Online\API\Requests\GetUsersRequest.cs" />
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
@ -131,9 +131,9 @@
<Compile Include="Beatmaps\Timing\BreakPeriod.cs" />
<Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
<Compile Include="Beatmaps\BeatmapMetrics.cs" />
<Compile Include="Database\DatabaseStore.cs" />
<Compile Include="Database\DatabaseBackedStore.cs" />
<Compile Include="Rulesets\RulesetInfo.cs" />
<Compile Include="Rulesets\Scoring\ScoreDatabase.cs" />
<Compile Include="Rulesets\Scoring\ScoreStore.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
<Compile Include="Graphics\Cursor\GameplayCursor.cs" />
@ -220,7 +220,7 @@
<Compile Include="Rulesets\Objects\Legacy\ConvertHitObjectType.cs" />
<Compile Include="Rulesets\Replays\ReplayButtonState.cs" />
<Compile Include="Rulesets\Replays\ReplayFrame.cs" />
<Compile Include="Rulesets\RulesetDatabase.cs" />
<Compile Include="Rulesets\RulesetStore.cs" />
<Compile Include="Rulesets\Scoring\Score.cs" />
<Compile Include="Rulesets\Scoring\ScoreProcessor.cs" />
<Compile Include="Rulesets\Timing\SpeedAdjustmentContainer.cs" />
@ -389,7 +389,7 @@
<Compile Include="Users\UpdateableAvatar.cs" />
<Compile Include="Users\User.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeControl.cs" />
<Compile Include="Beatmaps\BeatmapStore.cs" />
<Compile Include="Beatmaps\BeatmapManager.cs" />
<Compile Include="Beatmaps\IO\ArchiveReader.cs" />
<Compile Include="Beatmaps\Formats\BeatmapDecoder.cs" />
<Compile Include="Beatmaps\Formats\OsuLegacyDecoder.cs" />
@ -399,7 +399,6 @@
<Compile Include="Beatmaps\BeatmapMetadata.cs" />
<Compile Include="Beatmaps\BeatmapInfo.cs" />
<Compile Include="Beatmaps\BeatmapDifficulty.cs" />
<Compile Include="Beatmaps\BeatmapStoreWorkingBeatmap.cs" />
<Compile Include="Graphics\UserInterface\OsuButton.cs" />
<Compile Include="Overlays\Settings\Sections\MaintenanceSection.cs" />
<Compile Include="Overlays\Settings\SettingsSection.cs" />