mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Merge branch 'master' into realm-integration/live-queryable-fix
This commit is contained in:
@ -12,6 +12,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
@ -474,7 +475,7 @@ namespace osu.Game.Tests.Database
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportThenDeleteThenImport()
|
public void TestImportThenDeleteThenImportOptimisedPath()
|
||||||
{
|
{
|
||||||
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||||
{
|
{
|
||||||
@ -485,11 +486,39 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
deleteBeatmapSet(imported, realmFactory.Context);
|
deleteBeatmapSet(imported, realmFactory.Context);
|
||||||
|
|
||||||
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
||||||
|
|
||||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
Assert.IsFalse(imported.DeletePending);
|
||||||
|
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestImportThenDeleteThenImportNonOptimisedPath()
|
||||||
|
{
|
||||||
|
RunTestWithRealmAsync(async (realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
|
||||||
|
using var store = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
|
||||||
|
|
||||||
|
deleteBeatmapSet(imported, realmFactory.Context);
|
||||||
|
|
||||||
|
Assert.IsTrue(imported.DeletePending);
|
||||||
|
|
||||||
|
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
|
||||||
|
|
||||||
|
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||||
|
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||||
|
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||||
|
Assert.IsFalse(imported.DeletePending);
|
||||||
|
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -867,5 +896,15 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Assert.Fail(failureMessage);
|
Assert.Fail(failureMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class NonOptimisedBeatmapImporter : BeatmapImporter
|
||||||
|
{
|
||||||
|
public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
|
||||||
|
: base(realmFactory, storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool HasCustomHashFunction => true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,22 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAccessAfterAttach()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
|
{
|
||||||
|
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
|
||||||
|
|
||||||
|
var liveBeatmap = beatmap.ToLive();
|
||||||
|
|
||||||
|
using (var context = realmFactory.CreateContext())
|
||||||
|
context.Write(r => r.Add(beatmap));
|
||||||
|
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAccessNonManaged()
|
public void TestAccessNonManaged()
|
||||||
{
|
{
|
||||||
@ -51,7 +67,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
@ -80,7 +96,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
@ -121,7 +137,8 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
@ -159,7 +176,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
using (var threadContext = realmFactory.CreateContext())
|
||||||
@ -192,7 +209,7 @@ namespace osu.Game.Tests.Database
|
|||||||
using (var updateThreadContext = realmFactory.CreateContext())
|
using (var updateThreadContext = realmFactory.CreateContext())
|
||||||
{
|
{
|
||||||
updateThreadContext.All<RealmBeatmap>().SubscribeForNotifications(gotChange);
|
updateThreadContext.All<RealmBeatmap>().SubscribeForNotifications(gotChange);
|
||||||
RealmLive<RealmBeatmap>? liveBeatmap = null;
|
ILive<RealmBeatmap>? liveBeatmap = null;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
|
@ -288,9 +288,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
#region Implementation of IModelFileManager<in BeatmapSetInfo,in BeatmapSetFileInfo>
|
#region Implementation of IModelFileManager<in BeatmapSetInfo,in BeatmapSetFileInfo>
|
||||||
|
|
||||||
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents, string filename = null)
|
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents)
|
||||||
{
|
{
|
||||||
beatmapModelManager.ReplaceFile(model, file, contents, filename);
|
beatmapModelManager.ReplaceFile(model, file, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file)
|
public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file)
|
||||||
|
@ -453,13 +453,12 @@ namespace osu.Game.Database
|
|||||||
/// <param name="model">The item to operate on.</param>
|
/// <param name="model">The item to operate on.</param>
|
||||||
/// <param name="file">The existing file to be replaced.</param>
|
/// <param name="file">The existing file to be replaced.</param>
|
||||||
/// <param name="contents">The new file contents.</param>
|
/// <param name="contents">The new file contents.</param>
|
||||||
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
|
public void ReplaceFile(TModel model, TFileModel file, Stream contents)
|
||||||
public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null)
|
|
||||||
{
|
{
|
||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
DeleteFile(model, file);
|
DeleteFile(model, file);
|
||||||
AddFile(model, contents, filename ?? file.Filename);
|
AddFile(model, contents, file.Filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,7 @@ namespace osu.Game.Database
|
|||||||
/// <param name="model">The item to operate on.</param>
|
/// <param name="model">The item to operate on.</param>
|
||||||
/// <param name="file">The existing file to be replaced.</param>
|
/// <param name="file">The existing file to be replaced.</param>
|
||||||
/// <param name="contents">The new file contents.</param>
|
/// <param name="contents">The new file contents.</param>
|
||||||
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
|
void ReplaceFile(TModel model, TFileModel file, Stream contents);
|
||||||
void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete an existing file.
|
/// Delete an existing file.
|
||||||
|
@ -49,13 +49,13 @@ namespace osu.Game.Database
|
|||||||
return mapper.Map<T>(item);
|
return mapper.Map<T>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<RealmLive<T>> ToLive<T>(this IEnumerable<T> realmList)
|
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList)
|
||||||
where T : RealmObject, IHasGuidPrimaryKey
|
where T : RealmObject, IHasGuidPrimaryKey
|
||||||
{
|
{
|
||||||
return realmList.Select(l => new RealmLive<T>(l)).ToList();
|
return realmList.Select(l => new RealmLive<T>(l)).Cast<ILive<T>>().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RealmLive<T> ToLive<T>(this T realmObject)
|
public static ILive<T> ToLive<T>(this T realmObject)
|
||||||
where T : RealmObject, IHasGuidPrimaryKey
|
where T : RealmObject, IHasGuidPrimaryKey
|
||||||
{
|
{
|
||||||
return new RealmLive<T>(realmObject);
|
return new RealmLive<T>(realmObject);
|
||||||
|
@ -16,6 +16,7 @@ namespace osu.Game.Models
|
|||||||
{
|
{
|
||||||
public RealmFile File { get; set; } = null!;
|
public RealmFile File { get; set; } = null!;
|
||||||
|
|
||||||
|
// [Indexed] cannot be used on `EmbeddedObject`s as it only applies to top-level queries. May need to reconsider this if performance becomes a concern.
|
||||||
public string Filename { get; set; } = null!;
|
public string Filename { get; set; } = null!;
|
||||||
|
|
||||||
public RealmNamedFileUsage(RealmFile file, string filename)
|
public RealmNamedFileUsage(RealmFile file, string filename)
|
||||||
|
@ -78,8 +78,8 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
using (var stream = info.OpenRead())
|
using (var stream = info.OpenRead())
|
||||||
{
|
{
|
||||||
if (oldFile != null)
|
if (oldFile != null)
|
||||||
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
|
beatmaps.DeleteFile(set, oldFile);
|
||||||
else
|
|
||||||
beatmaps.AddFile(set, stream, info.Name);
|
beatmaps.AddFile(set, stream, info.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,8 +105,7 @@ namespace osu.Game.Screens.Edit.Setup
|
|||||||
using (var stream = info.OpenRead())
|
using (var stream = info.OpenRead())
|
||||||
{
|
{
|
||||||
if (oldFile != null)
|
if (oldFile != null)
|
||||||
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
|
beatmaps.DeleteFile(set, oldFile);
|
||||||
else
|
|
||||||
beatmaps.AddFile(set, stream, info.Name);
|
beatmaps.AddFile(set, stream, info.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +171,7 @@ namespace osu.Game.Skinning
|
|||||||
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
|
||||||
|
|
||||||
if (oldFile != null)
|
if (oldFile != null)
|
||||||
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
|
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent);
|
||||||
else
|
else
|
||||||
skinModelManager.AddFile(skin.SkinInfo, streamContent, filename);
|
skinModelManager.AddFile(skin.SkinInfo, streamContent, filename);
|
||||||
}
|
}
|
||||||
|
@ -294,12 +294,8 @@ namespace osu.Game.Stores
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
|
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected virtual string ComputeHash(TModel item, ArchiveReader? reader = null)
|
protected virtual string ComputeHash(TModel item)
|
||||||
{
|
{
|
||||||
if (reader != null)
|
|
||||||
// fast hashing for cases where the item's files may not be populated.
|
|
||||||
return computeHashFast(reader);
|
|
||||||
|
|
||||||
// for now, concatenate all hashable files in the set to create a unique hash.
|
// for now, concatenate all hashable files in the set to create a unique hash.
|
||||||
MemoryStream hashable = new MemoryStream();
|
MemoryStream hashable = new MemoryStream();
|
||||||
|
|
||||||
@ -374,7 +370,7 @@ namespace osu.Game.Stores
|
|||||||
// TODO: look into rollback of file additions (or delayed commit).
|
// TODO: look into rollback of file additions (or delayed commit).
|
||||||
item.Files.AddRange(createFileInfos(archive, Files, realm));
|
item.Files.AddRange(createFileInfos(archive, Files, realm));
|
||||||
|
|
||||||
item.Hash = ComputeHash(item, archive);
|
item.Hash = ComputeHash(item);
|
||||||
|
|
||||||
// TODO: we may want to run this outside of the transaction.
|
// TODO: we may want to run this outside of the transaction.
|
||||||
await Populate(item, archive, realm, cancellationToken).ConfigureAwait(false);
|
await Populate(item, archive, realm, cancellationToken).ConfigureAwait(false);
|
||||||
@ -387,7 +383,9 @@ namespace osu.Game.Stores
|
|||||||
if (CanReuseExisting(existing, item))
|
if (CanReuseExisting(existing, item))
|
||||||
{
|
{
|
||||||
LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
|
||||||
|
|
||||||
existing.DeletePending = false;
|
existing.DeletePending = false;
|
||||||
|
transaction.Commit();
|
||||||
|
|
||||||
return existing.ToLive();
|
return existing.ToLive();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user