1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 06:12:58 +08:00

Add back transaction support for beatmap importing

This commit is contained in:
Dean Herbert 2017-10-17 15:00:27 +09:00
parent fe44a28d48
commit cd41862e3b
11 changed files with 84 additions and 48 deletions

View File

@ -57,6 +57,18 @@ namespace osu.Game.Beatmaps
private readonly Storage storage; private readonly Storage storage;
private BeatmapStore createBeatmapStore(Func<OsuDbContext> 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 Func<OsuDbContext> createContext;
private readonly FileStore files; private readonly FileStore files;
private readonly RulesetStore rulesets; private readonly RulesetStore rulesets;
@ -80,16 +92,13 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
public Func<Storage> GetStableStorage { private get; set; } public Func<Storage> GetStableStorage { private get; set; }
public BeatmapManager(Storage storage, FileStore files, OsuDbContext context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) public BeatmapManager(Storage storage, Func<OsuDbContext> context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
{ {
beatmaps = new BeatmapStore(context); createContext = context;
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps = createBeatmapStore(context);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); files = new FileStore(context, storage);
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
this.storage = storage; this.storage = storage;
this.files = files;
this.rulesets = rulesets; this.rulesets = rulesets;
this.api = api; this.api = api;
@ -160,13 +169,24 @@ namespace osu.Game.Beatmaps
/// <param name="archiveReader">The beatmap to be imported.</param> /// <param name="archiveReader">The beatmap to be imported.</param>
public BeatmapSetInfo Import(ArchiveReader archiveReader) public BeatmapSetInfo Import(ArchiveReader archiveReader)
{ {
BeatmapSetInfo set;
// let's only allow one concurrent import at a time for now. // let's only allow one concurrent import at a time for now.
lock (importLock) lock (importLock)
Import(set = importToStorage(archiveReader)); {
var context = createContext();
return set; using (var transaction = context.Database.BeginTransaction())
{
// create local stores so we can isolate and thread safely, and share a context/transaction.
var filesForImport = new FileStore(() => context, storage);
var beatmapsForImport = createBeatmapStore(() => context);
BeatmapSetInfo set = importToStorage(filesForImport, archiveReader);
beatmapsForImport.Add(set);
context.SaveChanges();
transaction.Commit();
return set;
}
}
} }
/// <summary> /// <summary>
@ -178,8 +198,7 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database. // If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return; if (beatmapSetInfo.ID != 0) return;
lock (beatmaps) createBeatmapStore(createContext).Add(beatmapSetInfo);
beatmaps.Add(beatmapSetInfo);
} }
/// <summary> /// <summary>
@ -322,15 +341,6 @@ namespace osu.Game.Beatmaps
return working; return working;
} }
/// <summary>
/// Reset the manager to an empty state.
/// </summary>
public void Reset()
{
lock (beatmaps)
beatmaps.Reset();
}
/// <summary> /// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s. /// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary> /// </summary>
@ -400,7 +410,7 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <param name="reader">The beatmap archive to be read.</param> /// <param name="reader">The beatmap archive to be read.</param>
/// <returns>The imported beatmap, or an existing instance if it is already present.</returns> /// <returns>The imported beatmap, or an existing instance if it is already present.</returns>
private BeatmapSetInfo importToStorage(ArchiveReader reader) private BeatmapSetInfo importToStorage(FileStore files, ArchiveReader reader)
{ {
// let's make sure there are actually .osu files to import. // let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));

View File

@ -20,8 +20,8 @@ namespace osu.Game.Beatmaps
public event Action<BeatmapInfo> BeatmapHidden; public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored; public event Action<BeatmapInfo> BeatmapRestored;
public BeatmapStore(OsuDbContext context) public BeatmapStore(Func<OsuDbContext> factory)
: base(context) : base(factory)
{ {
} }

View File

@ -10,12 +10,15 @@ namespace osu.Game.Database
public abstract class DatabaseBackedStore public abstract class DatabaseBackedStore
{ {
protected readonly Storage Storage; protected readonly Storage Storage;
protected readonly OsuDbContext Context;
protected DatabaseBackedStore(OsuDbContext context, Storage storage = null) private readonly Func<OsuDbContext> contextSource;
protected OsuDbContext Context => contextSource();
protected DatabaseBackedStore(Func<OsuDbContext> contextSource, Storage storage = null)
{ {
Storage = storage; Storage = storage;
Context = context; this.contextSource = contextSource;
try try
{ {

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/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"));
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.IO
public readonly ResourceStore<byte[]> Store; public readonly ResourceStore<byte[]> Store;
public FileStore(OsuDbContext context, Storage storage) : base(context, storage) public FileStore(Func<OsuDbContext> contextSource, Storage storage) : base(contextSource, storage)
{ {
Store = new NamespacedResourceStore<byte[]>(new StorageBackedResourceStore(storage), prefix); Store = new NamespacedResourceStore<byte[]>(new StorageBackedResourceStore(storage), prefix);
} }

View File

@ -1,6 +1,7 @@
// 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 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -14,8 +15,8 @@ namespace osu.Game.Input
{ {
public class KeyBindingStore : DatabaseBackedStore public class KeyBindingStore : DatabaseBackedStore
{ {
public KeyBindingStore(OsuDbContext context, RulesetStore rulesets, Storage storage = null) public KeyBindingStore(Func<OsuDbContext> contextSource, RulesetStore rulesets, Storage storage = null)
: base(context, storage) : base(contextSource, storage)
{ {
foreach (var info in rulesets.AvailableRulesets) foreach (var info in rulesets.AvailableRulesets)
{ {

View File

@ -81,16 +81,18 @@ namespace osu.Game
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
private OsuDbContext createDbContext() => new OsuDbContext(Host.Storage.GetDatabaseConnectionString(@"client")); private DatabaseContextFactory contextFactory;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
dependencies.Cache(contextFactory = new DatabaseContextFactory(Host));
dependencies.Cache(this); dependencies.Cache(this);
dependencies.Cache(LocalConfig); dependencies.Cache(LocalConfig);
using (var dbContext = createDbContext()) using (var context = contextFactory.GetContext())
dbContext.Database.Migrate(); context.Database.Migrate();
dependencies.Cache(API = new APIAccess dependencies.Cache(API = new APIAccess
{ {
@ -98,11 +100,11 @@ namespace osu.Game
Token = LocalConfig.Get<string>(OsuSetting.Token) Token = LocalConfig.Get<string>(OsuSetting.Token)
}); });
dependencies.Cache(RulesetStore = new RulesetStore(createDbContext())); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory.GetContext));
dependencies.Cache(FileStore = new FileStore(createDbContext(), Host.Storage)); dependencies.Cache(FileStore = new FileStore(contextFactory.GetContext, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, createDbContext(), RulesetStore, API, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, createDbContext(), Host, BeatmapManager, RulesetStore)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(createDbContext(), RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore));
dependencies.Cache(new OsuColour()); dependencies.Cache(new OsuColour());
//this completely overrides the framework default. will need to change once we make a proper FontStore. //this completely overrides the framework default. will need to change once we make a proper FontStore.

View File

@ -26,8 +26,8 @@ namespace osu.Game.Rulesets
loadRulesetFromFile(file); loadRulesetFromFile(file);
} }
public RulesetStore(OsuDbContext context) public RulesetStore(Func<OsuDbContext> factory)
: base(context) : base(factory)
{ {
} }

View File

@ -1,6 +1,7 @@
// 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 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -25,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) // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
private ScoreIPCChannel ipc; private ScoreIPCChannel ipc;
public ScoreStore(Storage storage, OsuDbContext context, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(context) public ScoreStore(Storage storage, Func<OsuDbContext> factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
{ {
this.storage = storage; this.storage = storage;
this.beatmaps = beatmaps; this.beatmaps = beatmaps;

View File

@ -1,13 +1,13 @@
// 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 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
@ -25,8 +25,6 @@ namespace osu.Game.Tests.Visual
private DependencyContainer dependencies; private DependencyContainer dependencies;
private FileStore files;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -40,9 +38,10 @@ namespace osu.Game.Tests.Visual
var dbConnectionString = storage.GetDatabaseConnectionString(@"client"); var dbConnectionString = storage.GetDatabaseConnectionString(@"client");
dependencies.Cache(rulesets = new RulesetStore(new OsuDbContext(dbConnectionString))); Func<OsuDbContext> contextFactory = () => new OsuDbContext(dbConnectionString);
dependencies.Cache(files = new FileStore(new OsuDbContext(dbConnectionString), storage));
dependencies.Cache(manager = new BeatmapManager(storage, files, new OsuDbContext(dbConnectionString), rulesets, null)); dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null));
for (int i = 0; i < 100; i += 10) for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i)); manager.Import(createTestBeatmapSet(i));

View File

@ -285,6 +285,7 @@
<Compile Include="Beatmaps\Drawables\BeatmapPanel.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapPanel.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapSetCover.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapSetCover.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapSetHeader.cs" /> <Compile Include="Beatmaps\Drawables\BeatmapSetHeader.cs" />
<Compile Include="Database\DatabaseContextFactory.cs" />
<Compile Include="Migrations\20171014052545_Init.cs" /> <Compile Include="Migrations\20171014052545_Init.cs" />
<Compile Include="Migrations\20171014052545_Init.designer.cs"> <Compile Include="Migrations\20171014052545_Init.designer.cs">
<DependentUpon>20171014052545_Init.cs</DependentUpon> <DependentUpon>20171014052545_Init.cs</DependentUpon>