mirror of
https://github.com/ppy/osu.git
synced 2025-01-15 10:02:59 +08:00
DatabaseWriteUsage
This commit is contained in:
parent
cc948d688f
commit
edc3638175
@ -63,12 +63,10 @@ namespace osu.Game.Tests.Visual
|
|||||||
var storage = new TestStorage(@"TestCasePlaySongSelect");
|
var storage = new TestStorage(@"TestCasePlaySongSelect");
|
||||||
|
|
||||||
// this is by no means clean. should be replacing inside of OsuGameBase somehow.
|
// this is by no means clean. should be replacing inside of OsuGameBase somehow.
|
||||||
var context = new OsuDbContext();
|
DatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext());
|
||||||
|
|
||||||
OsuDbContext contextFactory() => context;
|
dependencies.Cache(rulesets = new RulesetStore(factory));
|
||||||
|
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null)
|
||||||
dependencies.Cache(rulesets = new RulesetStore(contextFactory));
|
|
||||||
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
|
|
||||||
{
|
{
|
||||||
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
|
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
|
||||||
});
|
});
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public WorkingBeatmap DefaultBeatmap { private get; set; }
|
public WorkingBeatmap DefaultBeatmap { private get; set; }
|
||||||
|
|
||||||
private readonly Func<OsuDbContext> createContext;
|
private readonly DatabaseContextFactory contextFactory;
|
||||||
|
|
||||||
private readonly FileStore files;
|
private readonly FileStore files;
|
||||||
|
|
||||||
@ -85,29 +85,18 @@ namespace osu.Game.Beatmaps
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<Storage> GetStableStorage { private get; set; }
|
public Func<Storage> GetStableStorage { private get; set; }
|
||||||
|
|
||||||
private void refreshImportContext()
|
public BeatmapManager(Storage storage, DatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
|
||||||
{
|
{
|
||||||
lock (importContextLock)
|
this.contextFactory = contextFactory;
|
||||||
{
|
|
||||||
importContext?.Value?.Dispose();
|
|
||||||
|
|
||||||
importContext = new Lazy<OsuDbContext>(() =>
|
beatmaps = new BeatmapStore(contextFactory);
|
||||||
{
|
|
||||||
var c = createContext();
|
|
||||||
c.Database.AutoTransactionsEnabled = false;
|
|
||||||
return c;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public BeatmapManager(Storage storage, Func<OsuDbContext> context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
|
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
|
||||||
{
|
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
|
||||||
createContext = context;
|
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||||
|
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||||
|
|
||||||
refreshImportContext();
|
files = new FileStore(contextFactory, storage);
|
||||||
|
|
||||||
beatmaps = getBeatmapStoreWithContext(context);
|
|
||||||
files = new FileStore(context, storage);
|
|
||||||
|
|
||||||
this.rulesets = rulesets;
|
this.rulesets = rulesets;
|
||||||
this.api = api;
|
this.api = api;
|
||||||
@ -170,7 +159,6 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
e = e.InnerException ?? e;
|
e = e.InnerException ?? e;
|
||||||
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
|
Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
|
||||||
refreshImportContext();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,80 +166,57 @@ namespace osu.Game.Beatmaps
|
|||||||
return imported;
|
return imported;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object importContextLock = new object();
|
|
||||||
private Lazy<OsuDbContext> importContext;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import a beatmap from an <see cref="ArchiveReader"/>.
|
/// Import a beatmap from an <see cref="ArchiveReader"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="archive">The beatmap to be imported.</param>
|
/// <param name="archive">The beatmap to be imported.</param>
|
||||||
public BeatmapSetInfo Import(ArchiveReader archive)
|
public BeatmapSetInfo Import(ArchiveReader archive)
|
||||||
{
|
{
|
||||||
// let's only allow one concurrent import at a time for now
|
using ( contextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes.
|
||||||
lock (importContextLock)
|
|
||||||
{
|
{
|
||||||
var context = importContext.Value;
|
// create a new set info (don't yet add to database)
|
||||||
|
var beatmapSet = createBeatmapSetInfo(archive);
|
||||||
|
|
||||||
using (var transaction = context.BeginTransaction())
|
// check if this beatmap has already been imported and exit early if so
|
||||||
|
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
|
||||||
|
if (existingHashMatch != null)
|
||||||
{
|
{
|
||||||
// create a new set info (don't yet add to database)
|
undelete(existingHashMatch);
|
||||||
var beatmapSet = createBeatmapSetInfo(archive);
|
return existingHashMatch;
|
||||||
|
|
||||||
// check if this beatmap has already been imported and exit early if so
|
|
||||||
var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash);
|
|
||||||
if (existingHashMatch != null)
|
|
||||||
{
|
|
||||||
undelete(beatmaps, files, existingHashMatch);
|
|
||||||
return existingHashMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if a set already exists with the same online id
|
|
||||||
if (beatmapSet.OnlineBeatmapSetID != null)
|
|
||||||
{
|
|
||||||
var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID);
|
|
||||||
if (existingOnlineId != null)
|
|
||||||
{
|
|
||||||
// {Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962…}
|
|
||||||
|
|
||||||
Delete(existingOnlineId);
|
|
||||||
beatmaps.Cleanup(s => s.ID == existingOnlineId.ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmapSet.Files = createFileInfos(archive, getFileStoreWithContext(context));
|
|
||||||
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
|
||||||
|
|
||||||
// remove metadata from difficulties where it matches the set
|
|
||||||
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
|
||||||
if (beatmapSet.Metadata.Equals(b.Metadata))
|
|
||||||
b.Metadata = null;
|
|
||||||
|
|
||||||
// import to beatmap store
|
|
||||||
import(beatmapSet, context);
|
|
||||||
|
|
||||||
context.SaveChanges(transaction);
|
|
||||||
return beatmapSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if a set already exists with the same online id
|
||||||
|
if (beatmapSet.OnlineBeatmapSetID != null)
|
||||||
|
{
|
||||||
|
var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID);
|
||||||
|
if (existingOnlineId != null)
|
||||||
|
{
|
||||||
|
// {Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962…}
|
||||||
|
|
||||||
|
Delete(existingOnlineId);
|
||||||
|
beatmaps.Cleanup(s => s.ID == existingOnlineId.ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
beatmapSet.Files = createFileInfos(archive, files);
|
||||||
|
beatmapSet.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
|
|
||||||
|
// remove metadata from difficulties where it matches the set
|
||||||
|
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
|
||||||
|
if (beatmapSet.Metadata.Equals(b.Metadata))
|
||||||
|
b.Metadata = null;
|
||||||
|
|
||||||
|
// import to beatmap store
|
||||||
|
Import(beatmapSet);
|
||||||
|
return beatmapSet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
|
/// Import a beatmap from a <see cref="BeatmapSetInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmapSetInfo">The beatmap to be imported.</param>
|
/// <param name="beatmapSet">The beatmap to be imported.</param>
|
||||||
public void Import(BeatmapSetInfo beatmapSetInfo)
|
public void Import(BeatmapSetInfo beatmapSet) => beatmaps.Add(beatmapSet);
|
||||||
{
|
|
||||||
lock (importContextLock)
|
|
||||||
{
|
|
||||||
var context = importContext.Value;
|
|
||||||
|
|
||||||
using (var transaction = context.BeginTransaction())
|
|
||||||
{
|
|
||||||
import(beatmapSetInfo, context);
|
|
||||||
context.SaveChanges(transaction);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads a beatmap.
|
/// Downloads a beatmap.
|
||||||
@ -350,26 +315,22 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
/// <param name="beatmapSet">The beatmap set to delete.</param>
|
||||||
public void Delete(BeatmapSetInfo beatmapSet)
|
public void Delete(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
lock (importContextLock)
|
using (var db = contextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var context = importContext.Value;
|
var context = db.Context;
|
||||||
|
|
||||||
using (var transaction = context.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);
|
||||||
|
|
||||||
|
if (beatmaps.Delete(beatmapSet))
|
||||||
{
|
{
|
||||||
context.ChangeTracker.AutoDetectChangesEnabled = false;
|
if (!beatmapSet.Protected)
|
||||||
|
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
||||||
// 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);
|
|
||||||
|
|
||||||
if (getBeatmapStoreWithContext(context).Delete(beatmapSet))
|
|
||||||
{
|
|
||||||
if (!beatmapSet.Protected)
|
|
||||||
getFileStoreWithContext(context).Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
|
||||||
context.SaveChanges(transaction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,19 +378,11 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapSet.Protected)
|
if (beatmapSet.Protected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
lock (importContextLock)
|
using (var db = contextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var context = importContext.Value;
|
db.Context.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||||
|
undelete(beatmapSet);
|
||||||
using (var transaction = context.BeginTransaction())
|
db.Context.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||||
{
|
|
||||||
context.ChangeTracker.AutoDetectChangesEnabled = false;
|
|
||||||
|
|
||||||
undelete(getBeatmapStoreWithContext(context), getFileStoreWithContext(context), beatmapSet);
|
|
||||||
|
|
||||||
context.ChangeTracker.AutoDetectChangesEnabled = true;
|
|
||||||
context.SaveChanges(transaction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,7 +405,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmaps">The store to restore beatmaps from.</param>
|
/// <param name="beatmaps">The store to restore beatmaps from.</param>
|
||||||
/// <param name="files">The store to restore beatmap files from.</param>
|
/// <param name="files">The store to restore beatmap files from.</param>
|
||||||
/// <param name="beatmapSet">The beatmap to restore.</param>
|
/// <param name="beatmapSet">The beatmap to restore.</param>
|
||||||
private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet)
|
private void undelete(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
if (!beatmaps.Undelete(beatmapSet)) return;
|
if (!beatmaps.Undelete(beatmapSet)) return;
|
||||||
|
|
||||||
@ -578,11 +531,6 @@ namespace osu.Game.Beatmaps
|
|||||||
notification.State = ProgressNotificationState.Completed;
|
notification.State = ProgressNotificationState.Completed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Import a <see cref="BeatmapSetInfo"/> into the beatmap store.
|
|
||||||
/// </summary>
|
|
||||||
private void import(BeatmapSetInfo beatmapSet, OsuDbContext context) => getBeatmapStoreWithContext(context).Add(beatmapSet);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
/// Creates an <see cref="ArchiveReader"/> from a valid storage path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -689,19 +637,5 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
return beatmapInfos;
|
return beatmapInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileStore getFileStoreWithContext(OsuDbContext context) => new FileStore(() => context, files.Storage);
|
|
||||||
|
|
||||||
private BeatmapStore getBeatmapStoreWithContext(OsuDbContext context) => getBeatmapStoreWithContext(() => context);
|
|
||||||
|
|
||||||
private BeatmapStore getBeatmapStoreWithContext(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ 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(Func<OsuDbContext> factory)
|
public BeatmapStore(DatabaseContextFactory factory)
|
||||||
: base(factory)
|
: base(factory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -31,24 +31,25 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapSet">The beatmap to add.</param>
|
/// <param name="beatmapSet">The beatmap to add.</param>
|
||||||
public void Add(BeatmapSetInfo beatmapSet)
|
public void Add(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var db = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
|
|
||||||
{
|
{
|
||||||
// If we detect a new metadata object it'll be attached to the current context so it can be reused
|
var context = db.Context;
|
||||||
// to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
|
|
||||||
// of the corresponding table (.Set<BeatmapMetadata>()) for matching entries to our criteria.
|
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
|
||||||
var contextMetadata = context.Set<BeatmapMetadata>().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
|
{
|
||||||
if (contextMetadata != null)
|
// If we detect a new metadata object it'll be attached to the current context so it can be reused
|
||||||
beatmap.Metadata = contextMetadata;
|
// to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
|
||||||
else
|
// of the corresponding table (.Set<BeatmapMetadata>()) for matching entries to our criteria.
|
||||||
context.BeatmapMetadata.Attach(beatmap.Metadata);
|
var contextMetadata = context.Set<BeatmapMetadata>().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
|
||||||
|
if (contextMetadata != null)
|
||||||
|
beatmap.Metadata = contextMetadata;
|
||||||
|
else
|
||||||
|
context.BeatmapMetadata.Attach(beatmap.Metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.BeatmapSetInfo.Attach(beatmapSet);
|
||||||
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.BeatmapSetInfo.Attach(beatmapSet);
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapSetAdded?.Invoke(beatmapSet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -59,10 +60,8 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
BeatmapSetRemoved?.Invoke(beatmapSet);
|
BeatmapSetRemoved?.Invoke(beatmapSet);
|
||||||
|
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
usage.Context.BeatmapSetInfo.Update(beatmapSet);
|
||||||
context.BeatmapSetInfo.Update(beatmapSet);
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapSetAdded?.Invoke(beatmapSet);
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
||||||
}
|
}
|
||||||
@ -74,13 +73,13 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
||||||
public bool Delete(BeatmapSetInfo beatmapSet)
|
public bool Delete(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using ( ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
Refresh(ref beatmapSet, BeatmapSets);
|
||||||
|
|
||||||
Refresh(ref beatmapSet, BeatmapSets);
|
if (beatmapSet.DeletePending) return false;
|
||||||
|
beatmapSet.DeletePending = true;
|
||||||
if (beatmapSet.DeletePending) return false;
|
}
|
||||||
beatmapSet.DeletePending = true;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapSetRemoved?.Invoke(beatmapSet);
|
BeatmapSetRemoved?.Invoke(beatmapSet);
|
||||||
return true;
|
return true;
|
||||||
@ -93,13 +92,13 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
|
||||||
public bool Undelete(BeatmapSetInfo beatmapSet)
|
public bool Undelete(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using ( ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
Refresh(ref beatmapSet, BeatmapSets);
|
||||||
|
|
||||||
Refresh(ref beatmapSet, BeatmapSets);
|
if (!beatmapSet.DeletePending) return false;
|
||||||
|
beatmapSet.DeletePending = false;
|
||||||
if (!beatmapSet.DeletePending) return false;
|
}
|
||||||
beatmapSet.DeletePending = false;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapSetAdded?.Invoke(beatmapSet);
|
BeatmapSetAdded?.Invoke(beatmapSet);
|
||||||
return true;
|
return true;
|
||||||
@ -112,15 +111,16 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||||
public bool Hide(BeatmapInfo beatmap)
|
public bool Hide(BeatmapInfo beatmap)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
Refresh(ref beatmap, Beatmaps);
|
||||||
|
|
||||||
Refresh(ref beatmap, Beatmaps);
|
if (beatmap.Hidden) return false;
|
||||||
|
beatmap.Hidden = true;
|
||||||
|
|
||||||
if (beatmap.Hidden) return false;
|
BeatmapHidden?.Invoke(beatmap);
|
||||||
beatmap.Hidden = true;
|
}
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapHidden?.Invoke(beatmap);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,13 +131,13 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
|
||||||
public bool Restore(BeatmapInfo beatmap)
|
public bool Restore(BeatmapInfo beatmap)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
Refresh(ref beatmap, Beatmaps);
|
||||||
|
|
||||||
Refresh(ref beatmap, Beatmaps);
|
if (!beatmap.Hidden) return false;
|
||||||
|
beatmap.Hidden = false;
|
||||||
if (!beatmap.Hidden) return false;
|
}
|
||||||
beatmap.Hidden = false;
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
BeatmapRestored?.Invoke(beatmap);
|
BeatmapRestored?.Invoke(beatmap);
|
||||||
return true;
|
return true;
|
||||||
@ -147,34 +147,36 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public void Cleanup(Expression<Func<BeatmapSetInfo, bool>> query)
|
public void Cleanup(Expression<Func<BeatmapSetInfo, bool>> query)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
|
var context = usage.Context;
|
||||||
|
|
||||||
var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
|
var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
|
||||||
.Where(query)
|
.Where(query)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
||||||
.Include(s => s.Metadata);
|
.Include(s => s.Metadata);
|
||||||
|
|
||||||
// metadata is M-N so we can't rely on cascades
|
// metadata is M-N so we can't rely on cascades
|
||||||
context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
|
context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
|
||||||
context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
|
context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
|
||||||
|
|
||||||
// todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
|
// 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)));
|
context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
|
||||||
|
|
||||||
// cascades down to beatmaps.
|
// cascades down to beatmaps.
|
||||||
context.BeatmapSetInfo.RemoveRange(purgeable);
|
context.BeatmapSetInfo.RemoveRange(purgeable);
|
||||||
context.SaveChanges();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQueryable<BeatmapSetInfo> BeatmapSets => GetContext().BeatmapSetInfo
|
public IQueryable<BeatmapSetInfo> BeatmapSets => ContextFactory.Get().BeatmapSetInfo
|
||||||
.Include(s => s.Metadata)
|
.Include(s => s.Metadata)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
|
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
||||||
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
||||||
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
|
.Include(s => s.Files).ThenInclude(f => f.FileInfo);
|
||||||
|
|
||||||
public IQueryable<BeatmapInfo> Beatmaps => GetContext().BeatmapInfo
|
public IQueryable<BeatmapInfo> Beatmaps => ContextFactory.Get().BeatmapInfo
|
||||||
.Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
|
.Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
|
||||||
.Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo)
|
.Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo)
|
||||||
.Include(b => b.Metadata)
|
.Include(b => b.Metadata)
|
||||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
public event Action SettingChanged;
|
public event Action SettingChanged;
|
||||||
|
|
||||||
public SettingsStore(Func<OsuDbContext> createContext)
|
public SettingsStore(DatabaseContextFactory contextFactory)
|
||||||
: base(createContext)
|
: base(contextFactory)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,19 +24,16 @@ namespace osu.Game.Configuration
|
|||||||
/// <param name="variant">An optional variant.</param>
|
/// <param name="variant">An optional variant.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
|
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
|
||||||
GetContext().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
ContextFactory.Get().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||||
|
|
||||||
public void Update(DatabasedSetting setting)
|
public void Update(DatabasedSetting setting)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
var newValue = setting.Value;
|
var newValue = setting.Value;
|
||||||
|
Refresh(ref setting);
|
||||||
Refresh(ref setting);
|
setting.Value = newValue;
|
||||||
|
}
|
||||||
setting.Value = newValue;
|
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
SettingChanged?.Invoke();
|
SettingChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 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 System.Threading;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
@ -17,9 +15,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="OsuDbContext"/> instance (separate from the shared context via <see cref="GetContext"/> for performing isolated operations.
|
/// Create a new <see cref="OsuDbContext"/> instance (separate from the shared context via <see cref="GetContext"/> for performing isolated operations.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly Func<OsuDbContext> CreateContext;
|
protected readonly DatabaseContextFactory ContextFactory;
|
||||||
|
|
||||||
private readonly ThreadLocal<OsuDbContext> queryContext;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refresh an instance potentially from a different thread with a local context-tracked instance.
|
/// Refresh an instance potentially from a different thread with a local context-tracked instance.
|
||||||
@ -29,33 +25,27 @@ namespace osu.Game.Database
|
|||||||
/// <typeparam name="T">A valid EF-stored type.</typeparam>
|
/// <typeparam name="T">A valid EF-stored type.</typeparam>
|
||||||
protected virtual void Refresh<T>(ref T obj, IEnumerable<T> lookupSource = null) where T : class, IHasPrimaryKey
|
protected virtual void Refresh<T>(ref T obj, IEnumerable<T> lookupSource = null) where T : class, IHasPrimaryKey
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
if (context.Entry(obj).State != EntityState.Detached) return;
|
|
||||||
|
|
||||||
var id = obj.ID;
|
|
||||||
var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
|
|
||||||
if (foundObject != null)
|
|
||||||
{
|
{
|
||||||
obj = foundObject;
|
var context = usage.Context;
|
||||||
context.Entry(obj).Reload();
|
|
||||||
|
if (context.Entry(obj).State != EntityState.Detached) return;
|
||||||
|
|
||||||
|
var id = obj.ID;
|
||||||
|
var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
|
||||||
|
if (foundObject != null)
|
||||||
|
{
|
||||||
|
obj = foundObject;
|
||||||
|
context.Entry(obj).Reload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
context.Add(obj);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
context.Add(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected DatabaseBackedStore(DatabaseContextFactory contextFactory, Storage storage = null)
|
||||||
/// Retrieve a shared context for performing lookups (or write operations on the update thread, for now).
|
|
||||||
/// </summary>
|
|
||||||
protected OsuDbContext GetContext() => queryContext.Value;
|
|
||||||
|
|
||||||
protected DatabaseBackedStore(Func<OsuDbContext> createContext, Storage storage = null)
|
|
||||||
{
|
{
|
||||||
CreateContext = createContext;
|
ContextFactory = contextFactory;
|
||||||
|
|
||||||
// todo: while this seems to work quite well, we need to consider that contexts could enter a state where they are never cleaned up.
|
|
||||||
queryContext = new ThreadLocal<OsuDbContext>(CreateContext);
|
|
||||||
|
|
||||||
Storage = storage;
|
Storage = storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 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.Threading;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
@ -11,17 +12,70 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private const string database_name = @"client";
|
private const string database_name = @"client";
|
||||||
|
|
||||||
|
private ThreadLocal<OsuDbContext> threadContexts;
|
||||||
|
|
||||||
|
private readonly object writeLock = new object();
|
||||||
|
|
||||||
|
private OsuDbContext writeContext;
|
||||||
|
|
||||||
|
private volatile int currentWriteUsages;
|
||||||
|
|
||||||
public DatabaseContextFactory(GameHost host)
|
public DatabaseContextFactory(GameHost host)
|
||||||
{
|
{
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
recycleThreadContexts();
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
|
/// <summary>
|
||||||
|
/// Get a context for read-only usage.
|
||||||
|
/// </summary>
|
||||||
|
public OsuDbContext Get() => threadContexts.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
|
||||||
|
/// This method may block if a write is already active on a different thread.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A usage containing a usable context.</returns>
|
||||||
|
public DatabaseWriteUsage GetForWrite()
|
||||||
|
{
|
||||||
|
lock (writeLock)
|
||||||
|
{
|
||||||
|
var usage = new DatabaseWriteUsage(writeContext ?? (writeContext = threadContexts.Value), usageCompleted);
|
||||||
|
Interlocked.Increment(ref currentWriteUsages);
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void usageCompleted(DatabaseWriteUsage usage)
|
||||||
|
{
|
||||||
|
int usages = Interlocked.Decrement(ref currentWriteUsages);
|
||||||
|
if (usages == 0)
|
||||||
|
{
|
||||||
|
writeContext.Dispose();
|
||||||
|
writeContext = null;
|
||||||
|
|
||||||
|
// once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches.
|
||||||
|
recycleThreadContexts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recycleThreadContexts() => threadContexts = new ThreadLocal<OsuDbContext>(CreateContext);
|
||||||
|
|
||||||
|
protected virtual OsuDbContext CreateContext()
|
||||||
|
{
|
||||||
|
var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name));
|
||||||
|
ctx.Database.AutoTransactionsEnabled = false;
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
public void ResetDatabase()
|
public void ResetDatabase()
|
||||||
{
|
{
|
||||||
// todo: we probably want to make sure there are no active contexts before performing this operation.
|
lock (writeLock)
|
||||||
host.Storage.DeleteDatabase(database_name);
|
{
|
||||||
|
recycleThreadContexts();
|
||||||
|
host.Storage.DeleteDatabase(database_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
osu.Game/Database/DatabaseWriteUsage.cs
Normal file
28
osu.Game/Database/DatabaseWriteUsage.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class DatabaseWriteUsage : IDisposable
|
||||||
|
{
|
||||||
|
public readonly OsuDbContext Context;
|
||||||
|
private readonly IDbContextTransaction transaction;
|
||||||
|
private readonly Action<DatabaseWriteUsage> usageCompleted;
|
||||||
|
|
||||||
|
public DatabaseWriteUsage(OsuDbContext context, Action<DatabaseWriteUsage> onCompleted)
|
||||||
|
{
|
||||||
|
Context = context;
|
||||||
|
transaction = Context.BeginTransaction();
|
||||||
|
usageCompleted = onCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Context.SaveChanges(transaction);
|
||||||
|
usageCompleted?.Invoke(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
osu.Game/Database/SingletonContextFactory.cs
Normal file
21
osu.Game/Database/SingletonContextFactory.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class SingletonContextFactory : DatabaseContextFactory
|
||||||
|
{
|
||||||
|
private readonly OsuDbContext context;
|
||||||
|
|
||||||
|
public SingletonContextFactory(OsuDbContext context)
|
||||||
|
: base(null)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override OsuDbContext CreateContext()
|
||||||
|
{
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,86 +21,91 @@ namespace osu.Game.IO
|
|||||||
|
|
||||||
public new Storage Storage => base.Storage;
|
public new Storage Storage => base.Storage;
|
||||||
|
|
||||||
public FileStore(Func<OsuDbContext> createContext, Storage storage) : base(createContext, storage.GetStorageForDirectory(@"files"))
|
public FileStore(DatabaseContextFactory contextFactory, Storage storage) : base(contextFactory, storage.GetStorageForDirectory(@"files"))
|
||||||
{
|
{
|
||||||
Store = new StorageBackedResourceStore(Storage);
|
Store = new StorageBackedResourceStore(Storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FileInfo Add(Stream data, bool reference = true)
|
public FileInfo Add(Stream data, bool reference = true)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
string hash = data.ComputeSHA2Hash();
|
|
||||||
|
|
||||||
var existing = context.FileInfo.FirstOrDefault(f => f.Hash == hash);
|
|
||||||
|
|
||||||
var info = existing ?? new FileInfo { Hash = hash };
|
|
||||||
|
|
||||||
string path = info.StoragePath;
|
|
||||||
|
|
||||||
// we may be re-adding a file to fix missing store entries.
|
|
||||||
if (!Storage.Exists(path))
|
|
||||||
{
|
{
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
var context = usage.Context;
|
||||||
|
|
||||||
using (var output = Storage.GetStream(path, FileAccess.Write))
|
string hash = data.ComputeSHA2Hash();
|
||||||
data.CopyTo(output);
|
|
||||||
|
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
var existing = context.FileInfo.FirstOrDefault(f => f.Hash == hash);
|
||||||
|
|
||||||
|
var info = existing ?? new FileInfo { Hash = hash };
|
||||||
|
|
||||||
|
string path = info.StoragePath;
|
||||||
|
|
||||||
|
// we may be re-adding a file to fix missing store entries.
|
||||||
|
if (!Storage.Exists(path))
|
||||||
|
{
|
||||||
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
using (var output = Storage.GetStream(path, FileAccess.Write))
|
||||||
|
data.CopyTo(output);
|
||||||
|
|
||||||
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reference || existing == null)
|
||||||
|
Reference(info);
|
||||||
|
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reference || existing == null)
|
|
||||||
Reference(info);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reference(params FileInfo[] files) => reference(GetContext(), files);
|
public void Reference(params FileInfo[] files)
|
||||||
|
|
||||||
private void reference(OsuDbContext context, FileInfo[] files)
|
|
||||||
{
|
{
|
||||||
foreach (var f in files.GroupBy(f => f.ID))
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var refetch = context.Find<FileInfo>(f.First().ID) ?? f.First();
|
var context = usage.Context;
|
||||||
refetch.ReferenceCount += f.Count();
|
|
||||||
context.FileInfo.Update(refetch);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.SaveChanges();
|
foreach (var f in files.GroupBy(f => f.ID))
|
||||||
|
{
|
||||||
|
var refetch = context.Find<FileInfo>(f.First().ID) ?? f.First();
|
||||||
|
refetch.ReferenceCount += f.Count();
|
||||||
|
context.FileInfo.Update(refetch);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dereference(params FileInfo[] files) => dereference(GetContext(), files);
|
public void Dereference(params FileInfo[] files)
|
||||||
|
|
||||||
private void dereference(OsuDbContext context, FileInfo[] files)
|
|
||||||
{
|
{
|
||||||
foreach (var f in files.GroupBy(f => f.ID))
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var refetch = context.FileInfo.Find(f.Key);
|
var context = usage.Context;
|
||||||
refetch.ReferenceCount -= f.Count();
|
foreach (var f in files.GroupBy(f => f.ID))
|
||||||
context.FileInfo.Update(refetch);
|
{
|
||||||
|
var refetch = context.FileInfo.Find(f.Key);
|
||||||
|
refetch.ReferenceCount -= f.Count();
|
||||||
|
context.FileInfo.Update(refetch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Cleanup()
|
public override void Cleanup()
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
|
|
||||||
{
|
{
|
||||||
try
|
var context = usage.Context;
|
||||||
|
|
||||||
|
foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
|
||||||
{
|
{
|
||||||
Storage.Delete(f.StoragePath);
|
try
|
||||||
context.FileInfo.Remove(f);
|
{
|
||||||
}
|
Storage.Delete(f.StoragePath);
|
||||||
catch (Exception e)
|
context.FileInfo.Remove(f);
|
||||||
{
|
}
|
||||||
Logger.Error(e, $@"Could not delete beatmap {f}");
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $@"Could not delete beatmap {f}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,17 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
public event Action KeyBindingChanged;
|
public event Action KeyBindingChanged;
|
||||||
|
|
||||||
public KeyBindingStore(Func<OsuDbContext> createContext, RulesetStore rulesets, Storage storage = null)
|
public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null)
|
||||||
: base(createContext, storage)
|
: base(contextFactory, storage)
|
||||||
{
|
{
|
||||||
foreach (var info in rulesets.AvailableRulesets)
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var ruleset = info.CreateInstance();
|
foreach (var info in rulesets.AvailableRulesets)
|
||||||
foreach (var variant in ruleset.AvailableVariants)
|
{
|
||||||
insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant);
|
var ruleset = info.CreateInstance();
|
||||||
|
foreach (var variant in ruleset.AvailableVariants)
|
||||||
|
insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +34,10 @@ namespace osu.Game.Input
|
|||||||
|
|
||||||
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
|
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
using (var transaction = context.BeginTransaction())
|
|
||||||
{
|
{
|
||||||
|
var context = usage.Context;
|
||||||
|
|
||||||
// compare counts in database vs defaults
|
// compare counts in database vs defaults
|
||||||
foreach (var group in defaults.GroupBy(k => k.Action))
|
foreach (var group in defaults.GroupBy(k => k.Action))
|
||||||
{
|
{
|
||||||
@ -54,8 +57,6 @@ namespace osu.Game.Input
|
|||||||
Variant = variant
|
Variant = variant
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SaveChanges(transaction);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,19 +67,16 @@ namespace osu.Game.Input
|
|||||||
/// <param name="variant">An optional variant.</param>
|
/// <param name="variant">An optional variant.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public List<DatabasedKeyBinding> Query(int? rulesetId = null, int? variant = null) =>
|
public List<DatabasedKeyBinding> Query(int? rulesetId = null, int? variant = null) =>
|
||||||
GetContext().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
ContextFactory.Get().DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
|
||||||
|
|
||||||
public void Update(KeyBinding keyBinding)
|
public void Update(KeyBinding keyBinding)
|
||||||
{
|
{
|
||||||
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
|
using (ContextFactory.GetForWrite())
|
||||||
|
{
|
||||||
var context = GetContext();
|
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
|
||||||
|
Refresh(ref dbKeyBinding);
|
||||||
Refresh(ref dbKeyBinding);
|
dbKeyBinding.KeyCombination = keyBinding.KeyCombination;
|
||||||
|
}
|
||||||
dbKeyBinding.KeyCombination = keyBinding.KeyCombination;
|
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
KeyBindingChanged?.Invoke();
|
KeyBindingChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -106,12 +106,12 @@ namespace osu.Game
|
|||||||
Token = LocalConfig.Get<string>(OsuSetting.Token)
|
Token = LocalConfig.Get<string>(OsuSetting.Token)
|
||||||
});
|
});
|
||||||
|
|
||||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory.GetContext));
|
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
|
||||||
dependencies.Cache(FileStore = new FileStore(contextFactory.GetContext, Host.Storage));
|
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
|
||||||
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host));
|
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Host));
|
||||||
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore));
|
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
|
||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory.GetContext));
|
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||||
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.
|
||||||
@ -179,8 +179,8 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.GetContext())
|
using (var db = contextFactory.GetForWrite())
|
||||||
context.Migrate();
|
db.Context.Migrate();
|
||||||
}
|
}
|
||||||
catch (MigrationFailedException e)
|
catch (MigrationFailedException e)
|
||||||
{
|
{
|
||||||
@ -191,8 +191,8 @@ namespace osu.Game
|
|||||||
contextFactory.ResetDatabase();
|
contextFactory.ResetDatabase();
|
||||||
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
|
Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important);
|
||||||
|
|
||||||
using (var context = contextFactory.GetContext())
|
using (var db = contextFactory.GetForWrite())
|
||||||
context.Migrate();
|
db.Context.Migrate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets
|
|||||||
loadRulesetFromFile(file);
|
loadRulesetFromFile(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RulesetStore(Func<OsuDbContext> factory)
|
public RulesetStore(DatabaseContextFactory factory)
|
||||||
: base(factory)
|
: base(factory)
|
||||||
{
|
{
|
||||||
AddMissingRulesets();
|
AddMissingRulesets();
|
||||||
@ -56,47 +56,50 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
protected void AddMissingRulesets()
|
protected void AddMissingRulesets()
|
||||||
{
|
{
|
||||||
var context = GetContext();
|
using (var usage = ContextFactory.GetForWrite())
|
||||||
|
|
||||||
var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
|
|
||||||
|
|
||||||
//add all legacy modes in correct order
|
|
||||||
foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
|
|
||||||
{
|
{
|
||||||
if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null)
|
var context = usage.Context;
|
||||||
context.RulesetInfo.Add(r.RulesetInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.SaveChanges();
|
var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
|
||||||
|
|
||||||
//add any other modes
|
//add all legacy modes in correct order
|
||||||
foreach (var r in instances.Where(r => r.LegacyID < 0))
|
foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID))
|
||||||
if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null)
|
|
||||||
context.RulesetInfo.Add(r.RulesetInfo);
|
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
//perform a consistency check
|
|
||||||
foreach (var r in context.RulesetInfo)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var instance = r.CreateInstance();
|
if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == r.RulesetInfo.ID) == null)
|
||||||
|
context.RulesetInfo.Add(r.RulesetInfo);
|
||||||
r.Name = instance.Description;
|
|
||||||
r.ShortName = instance.ShortName;
|
|
||||||
|
|
||||||
r.Available = true;
|
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
|
context.SaveChanges();
|
||||||
|
|
||||||
|
//add any other modes
|
||||||
|
foreach (var r in instances.Where(r => r.LegacyID < 0))
|
||||||
|
if (context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == r.RulesetInfo.InstantiationInfo) == null)
|
||||||
|
context.RulesetInfo.Add(r.RulesetInfo);
|
||||||
|
|
||||||
|
context.SaveChanges();
|
||||||
|
|
||||||
|
//perform a consistency check
|
||||||
|
foreach (var r in context.RulesetInfo)
|
||||||
{
|
{
|
||||||
r.Available = false;
|
try
|
||||||
|
{
|
||||||
|
var instance = r.CreateInstance();
|
||||||
|
|
||||||
|
r.Name = instance.Description;
|
||||||
|
r.ShortName = instance.ShortName;
|
||||||
|
|
||||||
|
r.Available = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
r.Available = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.SaveChanges();
|
||||||
|
|
||||||
|
AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.SaveChanges();
|
|
||||||
|
|
||||||
AvailableRulesets = context.RulesetInfo.Where(r => r.Available).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void loadRulesetFromFile(string file)
|
private static void loadRulesetFromFile(string file)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
// Copyright (c) 2007-2018 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;
|
||||||
@ -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)
|
// 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, Func<OsuDbContext> factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
|
public ScoreStore(Storage storage, DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory)
|
||||||
{
|
{
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.beatmaps = beatmaps;
|
this.beatmaps = beatmaps;
|
||||||
|
@ -275,7 +275,9 @@
|
|||||||
<Compile Include="Configuration\DatabasedConfigManager.cs" />
|
<Compile Include="Configuration\DatabasedConfigManager.cs" />
|
||||||
<Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" />
|
<Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" />
|
||||||
<Compile Include="Database\DatabaseContextFactory.cs" />
|
<Compile Include="Database\DatabaseContextFactory.cs" />
|
||||||
|
<Compile Include="Database\DatabaseWriteUsage.cs" />
|
||||||
<Compile Include="Database\IHasPrimaryKey.cs" />
|
<Compile Include="Database\IHasPrimaryKey.cs" />
|
||||||
|
<Compile Include="Database\SingletonContextFactory.cs" />
|
||||||
<Compile Include="Graphics\Containers\LinkFlowContainer.cs" />
|
<Compile Include="Graphics\Containers\LinkFlowContainer.cs" />
|
||||||
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
|
<Compile Include="Graphics\Textures\LargeTextureStore.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user