1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 22:33:05 +08:00

Merge pull request #14903 from peppy/importer-returns-live

Add `ILive<T>` and use as return type of `Import` methods
This commit is contained in:
Dan Balasescu 2021-10-04 20:16:32 +09:00 committed by GitHub
commit 4bd1083388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 168 additions and 73 deletions

View File

@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
BeatmapSetInfo importedSet; ILive<BeatmapSetInfo> importedSet;
using (var stream = File.OpenRead(tempPath)) using (var stream = File.OpenRead(tempPath))
{ {
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
File.Delete(tempPath); File.Delete(tempPath);
var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
deleteBeatmapSet(imported, osu); deleteBeatmapSet(imported, osu);
} }
@ -172,8 +172,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
// but contents doesn't, so existing should still be used. // but contents doesn't, so existing should still be used.
Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID);
} }
finally finally
{ {
@ -226,8 +226,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
} }
finally finally
{ {
@ -278,8 +278,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
} }
finally finally
{ {
@ -329,8 +329,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
} }
finally finally
{ {
@ -570,8 +570,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await manager.Import(toImport); var imported = await manager.Import(toImport);
Assert.NotNull(imported); Assert.NotNull(imported);
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID);
Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID);
} }
finally finally
{ {
@ -706,7 +706,7 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
} }
finally finally
{ {
@ -759,8 +759,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu); ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
} }
finally finally
{ {
@ -915,7 +915,7 @@ namespace osu.Game.Tests.Beatmaps.IO
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
} }
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
@ -930,7 +930,7 @@ namespace osu.Game.Tests.Beatmaps.IO
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
} }
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)

View File

@ -156,7 +156,7 @@ namespace osu.Game.Tests.Online
{ {
public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>(); public TaskCompletionSource<bool> AllowImport = new TaskCompletionSource<bool>();
public Task<BeatmapSetInfo> CurrentImportTask { get; private set; } public Task<ILive<BeatmapSetInfo>> CurrentImportTask { get; private set; }
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
@ -194,7 +194,7 @@ namespace osu.Game.Tests.Online
this.testBeatmapManager = testBeatmapManager; this.testBeatmapManager = testBeatmapManager;
} }
public override async Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) public override async Task<ILive<BeatmapSetInfo>> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); await testBeatmapManager.AllowImport.Task.ConfigureAwait(false);
return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);

View File

@ -196,7 +196,7 @@ namespace osu.Game.Tests.Skins.IO
private async Task<SkinInfo> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) private async Task<SkinInfo> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
{ {
var skinManager = osu.Dependencies.Get<SkinManager>(); var skinManager = osu.Dependencies.Get<SkinManager>();
return await skinManager.Import(archive); return (await skinManager.Import(archive)).Value;
} }
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Skins
private void load() private void load()
{ {
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]);
beatmap.LoadTrack(); beatmap.LoadTrack();
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins
private void load() private void load()
{ {
var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result;
skin = skins.GetSkin(imported); skin = skins.GetSkin(imported.Value);
} }
[Test] [Test]

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("import beatmap with track", () => AddStep("import beatmap with track", () =>
{ {
var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result;
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First());
}); });
AddStep("bind to track change", () => AddStep("bind to track change", () =>

View File

@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}, },
} }
}).Result; }).Result.Value;
}); });
AddAssert($"import {i} succeeded", () => imported != null); AddAssert($"import {i} succeeded", () => imported != null);

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo
}, },
} }
}).Result; }).Result.Value;
}); });
} }
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation
OnlineScoreID = i, OnlineScoreID = i,
BeatmapInfo = beatmap.Beatmaps.First(), BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}).Result; }).Result.Value;
}); });
AddAssert($"import {i} succeeded", () => imported != null); AddAssert($"import {i} succeeded", () => imported != null);

View File

@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
}); });
AddStep("load room", () => AddStep("load room", () =>

View File

@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect
}).ToList() }).ToList()
}; };
return Game.BeatmapManager.Import(beatmapSet).Result; return Game.BeatmapManager.Import(beatmapSet).Result.Value;
} }
private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null); private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);

View File

@ -751,7 +751,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import huge difficulty count map", () => AddStep("import huge difficulty count map", () =>
{ {
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result; imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result.Value;
}); });
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));

View File

@ -85,7 +85,7 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0];
for (int i = 0; i < 50; i++) for (int i = 0; i < 50; i++)
{ {
@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface
User = new User { Username = "TestUser" }, User = new User { Username = "TestUser" },
}; };
importedScores.Add(scoreManager.Import(score).Result); importedScores.Add(scoreManager.Import(score).Result.Value);
} }
return dependencies; return dependencies;

View File

@ -90,8 +90,9 @@ namespace osu.Game.Beatmaps
} }
}; };
var working = beatmapModelManager.Import(set).Result; var imported = beatmapModelManager.Import(set).Result.Value;
return GetWorkingBeatmap(working.Beatmaps.First());
return GetWorkingBeatmap(imported.Beatmaps.First());
} }
#region Delegation to BeatmapModelManager (methods which previously existed locally). #region Delegation to BeatmapModelManager (methods which previously existed locally).
@ -177,7 +178,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Fired when the user requests to view the resulting import. /// Fired when the user requests to view the resulting import.
/// </summary> /// </summary>
public Action<IEnumerable<BeatmapSetInfo>> PresentImport { set => beatmapModelManager.PresentImport = value; } public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
/// <summary> /// <summary>
/// Delete a beatmap difficulty. /// Delete a beatmap difficulty.
@ -276,22 +277,22 @@ namespace osu.Game.Beatmaps
return beatmapModelManager.Import(tasks); return beatmapModelManager.Import(tasks);
} }
public Task<IEnumerable<BeatmapSetInfo>> Import(ProgressNotification notification, params ImportTask[] tasks) public Task<IEnumerable<ILive<BeatmapSetInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks)
{ {
return beatmapModelManager.Import(notification, tasks); return beatmapModelManager.Import(notification, tasks);
} }
public Task<BeatmapSetInfo> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<BeatmapSetInfo>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return beatmapModelManager.Import(task, lowPriority, cancellationToken); return beatmapModelManager.Import(task, lowPriority, cancellationToken);
} }
public Task<BeatmapSetInfo> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<BeatmapSetInfo>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return beatmapModelManager.Import(archive, lowPriority, cancellationToken); return beatmapModelManager.Import(archive, lowPriority, cancellationToken);
} }
public Task<BeatmapSetInfo> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<BeatmapSetInfo>> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken);
} }

View File

@ -30,7 +30,7 @@ namespace osu.Game.Database
/// </summary> /// </summary>
/// <typeparam name="TModel">The model type.</typeparam> /// <typeparam name="TModel">The model type.</typeparam>
/// <typeparam name="TFileModel">The associated file join type.</typeparam> /// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPresentImports<TModel> public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPostImports<TModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : class, INamedFileInfo, new() where TFileModel : class, INamedFileInfo, new()
{ {
@ -132,13 +132,13 @@ namespace osu.Game.Database
return Import(notification, tasks); return Import(notification, tasks);
} }
public async Task<IEnumerable<TModel>> Import(ProgressNotification notification, params ImportTask[] tasks) public async Task<IEnumerable<ILive<TModel>>> Import(ProgressNotification notification, params ImportTask[] tasks)
{ {
if (tasks.Length == 0) if (tasks.Length == 0)
{ {
notification.CompletionText = $"No {HumanisedModelName}s were found to import!"; notification.CompletionText = $"No {HumanisedModelName}s were found to import!";
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
return Enumerable.Empty<TModel>(); return Enumerable.Empty<ILive<TModel>>();
} }
notification.Progress = 0; notification.Progress = 0;
@ -146,7 +146,7 @@ namespace osu.Game.Database
int current = 0; int current = 0;
var imported = new List<TModel>(); var imported = new List<ILive<TModel>>();
bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size;
@ -200,12 +200,12 @@ namespace osu.Game.Database
? $"Imported {imported.First()}!" ? $"Imported {imported.First()}!"
: $"Imported {imported.Count} {HumanisedModelName}s!"; : $"Imported {imported.Count} {HumanisedModelName}s!";
if (imported.Count > 0 && PresentImport != null) if (imported.Count > 0 && PostImport != null)
{ {
notification.CompletionText += " Click to view."; notification.CompletionText += " Click to view.";
notification.CompletionClickAction = () => notification.CompletionClickAction = () =>
{ {
PresentImport?.Invoke(imported); PostImport?.Invoke(imported);
return true; return true;
}; };
} }
@ -224,11 +224,11 @@ namespace osu.Game.Database
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The imported model, if successful.</returns> /// <returns>The imported model, if successful.</returns>
public async Task<TModel> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) public async Task<ILive<TModel>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
TModel import; ILive<TModel> import;
using (ArchiveReader reader = task.GetReader()) using (ArchiveReader reader = task.GetReader())
import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false);
@ -243,13 +243,13 @@ namespace osu.Game.Database
} }
catch (Exception e) catch (Exception e)
{ {
LogForModel(import, $@"Could not delete original file after import ({task})", e); LogForModel(import?.Value, $@"Could not delete original file after import ({task})", e);
} }
return import; return import;
} }
public Action<IEnumerable<TModel>> PresentImport { protected get; set; } public Action<IEnumerable<ILive<TModel>>> PostImport { protected get; set; }
/// <summary> /// <summary>
/// Silently import an item from an <see cref="ArchiveReader"/>. /// Silently import an item from an <see cref="ArchiveReader"/>.
@ -257,7 +257,7 @@ namespace osu.Game.Database
/// <param name="archive">The archive to be imported.</param> /// <param name="archive">The archive to be imported.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
public Task<TModel> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<TModel>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -268,7 +268,7 @@ namespace osu.Game.Database
model = CreateModel(archive); model = CreateModel(archive);
if (model == null) if (model == null)
return Task.FromResult<TModel>(null); return Task.FromResult<ILive<TModel>>(new EntityFrameworkLive<TModel>(null));
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
@ -343,7 +343,7 @@ namespace osu.Game.Database
/// <param name="archive">An optional archive to use for model population.</param> /// <param name="archive">An optional archive to use for model population.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
public virtual async Task<TModel> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => public virtual async Task<ILive<TModel>> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
@ -369,7 +369,7 @@ namespace osu.Game.Database
{ {
LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) skipping import."); LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) skipping import.");
Undelete(existing); Undelete(existing);
return existing; return existing.ToEntityFrameworkLive();
} }
LogForModel(item, @"Found existing (optimised) but failed pre-check."); LogForModel(item, @"Found existing (optimised) but failed pre-check.");
@ -415,7 +415,7 @@ namespace osu.Game.Database
// existing item will be used; rollback new import and exit early. // existing item will be used; rollback new import and exit early.
rollback(); rollback();
flushEvents(true); flushEvents(true);
return existing; return existing.ToEntityFrameworkLive();
} }
LogForModel(item, @"Found existing but failed re-use check."); LogForModel(item, @"Found existing but failed re-use check.");
@ -448,7 +448,7 @@ namespace osu.Game.Database
} }
flushEvents(true); flushEvents(true);
return item; return item.ToEntityFrameworkLive();
}, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false);
/// <summary> /// <summary>

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
namespace osu.Game.Database
{
public class EntityFrameworkLive<T> : ILive<T> where T : class
{
public EntityFrameworkLive(T item)
{
Value = item;
}
public Guid ID => throw new InvalidOperationException();
public void PerformRead(Action<T> perform)
{
perform(Value);
}
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform)
{
return perform(Value);
}
public void PerformWrite(Action<T> perform)
{
perform(Value);
}
public T Value { get; }
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Database
{
public static class EntityFrameworkLiveExtensions
{
public static ILive<T> ToEntityFrameworkLive<T>(this T item)
where T : class
{
return new EntityFrameworkLive<T>(item);
}
}
}

View File

@ -11,6 +11,6 @@ namespace osu.Game.Database
{ {
[JsonIgnore] [JsonIgnore]
[PrimaryKey] [PrimaryKey]
Guid ID { get; set; } Guid ID { get; }
} }
} }

View File

@ -0,0 +1,42 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
namespace osu.Game.Database
{
/// <summary>
/// A wrapper to provide access to database backed classes in a thread-safe manner.
/// </summary>
/// <typeparam name="T">The databased type.</typeparam>
public interface ILive<out T> where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more.
{
Guid ID { get; }
/// <summary>
/// Perform a read operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
void PerformRead(Action<T> perform);
/// <summary>
/// Perform a read operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
TReturn PerformRead<TReturn>(Func<T, TReturn> perform);
/// <summary>
/// Perform a write operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
void PerformWrite(Action<T> perform);
/// <summary>
/// Resolve the value of this instance on the current thread's context.
/// </summary>
/// <remarks>
/// After resolving the data should not be passed between threads.
/// </remarks>
T Value { get; }
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Database
Task Import(params ImportTask[] tasks); Task Import(params ImportTask[] tasks);
Task<IEnumerable<TModel>> Import(ProgressNotification notification, params ImportTask[] tasks); Task<IEnumerable<ILive<TModel>>> Import(ProgressNotification notification, params ImportTask[] tasks);
/// <summary> /// <summary>
/// Import one <typeparamref name="TModel"/> from the filesystem and delete the file on success. /// Import one <typeparamref name="TModel"/> from the filesystem and delete the file on success.
@ -38,7 +38,7 @@ namespace osu.Game.Database
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The imported model, if successful.</returns> /// <returns>The imported model, if successful.</returns>
Task<TModel> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Silently import an item from an <see cref="ArchiveReader"/>. /// Silently import an item from an <see cref="ArchiveReader"/>.
@ -46,7 +46,7 @@ namespace osu.Game.Database
/// <param name="archive">The archive to be imported.</param> /// <param name="archive">The archive to be imported.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
Task<TModel> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Silently import an item from a <typeparamref name="TModel"/>. /// Silently import an item from a <typeparamref name="TModel"/>.
@ -55,7 +55,7 @@ namespace osu.Game.Database
/// <param name="archive">An optional archive to use for model population.</param> /// <param name="archive">An optional archive to use for model population.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
Task<TModel> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// A user displayable name for the model type associated with this manager. /// A user displayable name for the model type associated with this manager.

View File

@ -6,12 +6,12 @@ using System.Collections.Generic;
namespace osu.Game.Database namespace osu.Game.Database
{ {
public interface IPresentImports<TModel> public interface IPostImports<out TModel>
where TModel : class where TModel : class
{ {
/// <summary> /// <summary>
/// Fired when the user requests to view the resulting import. /// Fired when the user requests to view the resulting import.
/// </summary> /// </summary>
public Action<IEnumerable<TModel>> PresentImport { set; } public Action<IEnumerable<ILive<TModel>>> PostImport { set; }
} }
} }

View File

@ -624,10 +624,10 @@ namespace osu.Game
SkinManager.PostNotification = n => Notifications.Post(n); SkinManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
ScoreManager.PostNotification = n => Notifications.Post(n); ScoreManager.PostNotification = n => Notifications.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First()); ScoreManager.PostImport = items => PresentScore(items.First().Value);
// make config aware of how to lookup skins for on-screen display purposes. // make config aware of how to lookup skins for on-screen display purposes.
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.

View File

@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring namespace osu.Game.Scoring
{ {
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles, IPresentImports<ScoreInfo> public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles, IPostImports<ScoreInfo>
{ {
private readonly Scheduler scheduler; private readonly Scheduler scheduler;
private readonly Func<BeatmapDifficultyCache> difficulties; private readonly Func<BeatmapDifficultyCache> difficulties;
@ -299,22 +299,22 @@ namespace osu.Game.Scoring
public IEnumerable<string> HandledExtensions => scoreModelManager.HandledExtensions; public IEnumerable<string> HandledExtensions => scoreModelManager.HandledExtensions;
public Task<IEnumerable<ScoreInfo>> Import(ProgressNotification notification, params ImportTask[] tasks) public Task<IEnumerable<ILive<ScoreInfo>>> Import(ProgressNotification notification, params ImportTask[] tasks)
{ {
return scoreModelManager.Import(notification, tasks); return scoreModelManager.Import(notification, tasks);
} }
public Task<ScoreInfo> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<ScoreInfo>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return scoreModelManager.Import(task, lowPriority, cancellationToken); return scoreModelManager.Import(task, lowPriority, cancellationToken);
} }
public Task<ScoreInfo> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<ScoreInfo>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return scoreModelManager.Import(archive, lowPriority, cancellationToken); return scoreModelManager.Import(archive, lowPriority, cancellationToken);
} }
public Task<ScoreInfo> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) public Task<ILive<ScoreInfo>> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{ {
return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); return scoreModelManager.Import(item, archive, lowPriority, cancellationToken);
} }
@ -365,9 +365,9 @@ namespace osu.Game.Scoring
#region Implementation of IPresentImports<ScoreInfo> #region Implementation of IPresentImports<ScoreInfo>
public Action<IEnumerable<ScoreInfo>> PresentImport public Action<IEnumerable<ILive<ScoreInfo>>> PostImport
{ {
set => scoreModelManager.PresentImport = value; set => scoreModelManager.PostImport = value;
} }
#endregion #endregion

View File

@ -101,8 +101,12 @@ namespace osu.Game.Screens.Menu
// if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state.
// this could happen if a user has nuked their files store. for now, reimport to repair this. // this could happen if a user has nuked their files store. for now, reimport to repair this.
var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result;
import.Protected = true;
beatmaps.Update(import); import.PerformWrite(b =>
{
b.Protected = true;
beatmaps.Update(b);
});
loadThemedIntro(); loadThemedIntro();
} }

View File

@ -207,7 +207,7 @@ namespace osu.Game.Skinning
Name = skin.SkinInfo.Name + " (modified)", Name = skin.SkinInfo.Name + " (modified)",
Creator = skin.SkinInfo.Creator, Creator = skin.SkinInfo.Creator,
InstantiationInfo = skin.SkinInfo.InstantiationInfo, InstantiationInfo = skin.SkinInfo.InstantiationInfo,
}).Result; }).Result.Value;
} }
public void Save(Skin skin) public void Save(Skin skin)