1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 20:07:25 +08:00

Move implementation of updating a beatmap to BeatmapImporter

This commit is contained in:
Dean Herbert 2022-07-25 19:31:46 +09:00
parent 2363a3fb7b
commit 2e14d8730c
6 changed files with 89 additions and 59 deletions

View File

@ -237,7 +237,7 @@ namespace osu.Game.Tests.Online
internal class TestBeatmapModelDownloader : BeatmapModelDownloader
{
public TestBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> importer, IAPIProvider apiProvider)
public TestBeatmapModelDownloader(BeatmapManager importer, IAPIProvider apiProvider)
: base(importer, apiProvider)
{
}

View File

@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging;
@ -16,6 +18,7 @@ using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using Realms;
@ -38,6 +41,59 @@ namespace osu.Game.Beatmaps
{
}
public async Task<IEnumerable<Live<BeatmapSetInfo>>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
{
var imported = await Import(notification, importTask);
Debug.Assert(imported.Count() == 1);
imported.First().PerformWrite(updated =>
{
Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database);
original = updated.Realm.Find<BeatmapSetInfo>(original.ID);
// Generally the import process will do this for us if the OnlineIDs match,
// but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated).
original.DeletePending = true;
// Transfer local values which should be persisted across a beatmap update.
updated.DateAdded = original.DateAdded;
foreach (var beatmap in original.Beatmaps.ToArray())
{
var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash);
if (updatedBeatmap != null)
{
// If the updated beatmap matches an existing one, transfer any user data across..
if (beatmap.Scores.Any())
{
Logger.Log($"Transferring {beatmap.Scores.Count()} scores for unchanged difficulty \"{beatmap}\"", LoggingTarget.Database);
foreach (var score in beatmap.Scores)
score.BeatmapInfo = updatedBeatmap;
}
// ..then nuke the old beatmap completely.
// this is done instead of a soft deletion to avoid a user potentially creating weird
// interactions, like restoring the outdated beatmap then updating a second time
// (causing user data to be wiped).
original.Beatmaps.Remove(beatmap);
}
else
{
// If the beatmap differs in the original, leave it in a soft-deleted state but reset online info.
// This caters to the case where a user has made modifications they potentially want to restore,
// but after restoring we want to ensure it can't be used to trigger an update of the beatmap.
beatmap.ResetOnlineInfo();
}
}
});
return imported;
}
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz";
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)

View File

@ -408,6 +408,9 @@ namespace osu.Game.Beatmaps
Realm.Run(r => Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
}
public Task<IEnumerable<Live<BeatmapSetInfo>>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) =>
beatmapImporter.ImportAsUpdate(notification, importTask, original);
#region Implementation of ICanAcceptFiles
public Task Import(params string[] paths) => beatmapImporter.Import(paths);

View File

@ -1,77 +1,39 @@
// 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.Linq;
using osu.Framework.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Beatmaps
{
public class BeatmapModelDownloader : ModelDownloader<BeatmapSetInfo, IBeatmapSetInfo>
{
private readonly BeatmapManager beatmapManager;
protected override ArchiveDownloadRequest<IBeatmapSetInfo> CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) =>
new DownloadBeatmapSetRequest(set, minimiseDownloadSize);
public override ArchiveDownloadRequest<IBeatmapSetInfo>? GetExistingDownload(IBeatmapSetInfo model)
=> CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID);
public BeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api)
public BeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api)
: base(beatmapImporter, api)
{
beatmapManager = beatmapImporter;
}
public bool Update(BeatmapSetInfo model)
protected override Task<IEnumerable<Live<BeatmapSetInfo>>> Import(ProgressNotification notification, string filename, BeatmapSetInfo? originalModel)
{
return Download(model, false, onSuccess);
if (originalModel != null)
return beatmapManager.ImportAsUpdate(notification, new ImportTask(filename), originalModel);
void onSuccess(Live<BeatmapSetInfo> imported)
{
imported.PerformWrite(updated =>
{
Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database);
var original = updated.Realm.Find<BeatmapSetInfo>(model.ID);
// Generally the import process will do this for us if the OnlineIDs match,
// but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated).
original.DeletePending = true;
// Transfer local values which should be persisted across a beatmap update.
updated.DateAdded = original.DateAdded;
foreach (var beatmap in original.Beatmaps.ToArray())
{
var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash);
if (updatedBeatmap != null)
{
// If the updated beatmap matches an existing one, transfer any user data across..
if (beatmap.Scores.Any())
{
Logger.Log($"Transferring {beatmap.Scores.Count()} scores for unchanged difficulty \"{beatmap}\"", LoggingTarget.Database);
foreach (var score in beatmap.Scores)
score.BeatmapInfo = updatedBeatmap;
}
// ..then nuke the old beatmap completely.
// this is done instead of a soft deletion to avoid a user potentially creating weird
// interactions, like restoring the outdated beatmap then updating a second time
// (causing user data to be wiped).
original.Beatmaps.Remove(beatmap);
}
else
{
// If the beatmap differs in the original, leave it in a soft-deleted state but reset online info.
// This caters to the case where a user has made modifications they potentially want to restore,
// but after restoring we want to ensure it can't be used to trigger an update of the beatmap.
beatmap.ResetOnlineInfo();
}
}
});
}
return base.Import(notification, filename, null);
}
public bool Update(BeatmapSetInfo model) => Download(model, false, model);
}
}

View File

@ -11,7 +11,6 @@ using System.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
@ -109,7 +108,7 @@ namespace osu.Game.Beatmaps.Drawables
private class BundledBeatmapModelDownloader : BeatmapModelDownloader
{
public BundledBeatmapModelDownloader(IModelImporter<BeatmapSetInfo> beatmapImporter, IAPIProvider api)
public BundledBeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api)
: base(beatmapImporter, api)
{
}

View File

@ -44,7 +44,7 @@ namespace osu.Game.Database
public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null);
protected bool Download(T model, bool minimiseDownloadSize, Action<Live<TModel>>? onSuccess)
protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel)
{
if (!canDownload(model)) return false;
@ -66,12 +66,10 @@ namespace osu.Game.Database
Task.Factory.StartNew(async () =>
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false);
var imported = await Import(notification, filename, originalModel).ConfigureAwait(false);
// for now a failed import will be marked as a failed download for simplicity.
if (imported.Any())
onSuccess?.Invoke(imported.Single());
else
if (!imported.Any())
DownloadFailed?.Invoke(request);
CurrentDownloads.Remove(request);
@ -107,6 +105,18 @@ namespace osu.Game.Database
}
}
/// <summary>
/// Run the post-download import for the model.
/// </summary>
/// <param name="notification">The notification to update.</param>
/// <param name="filename">The path of the temporary downloaded file.</param>
/// <param name="originalModel">An optional model for update scenarios, to be used as a reference.</param>
/// <returns>The imported model.</returns>
protected virtual Task<IEnumerable<Live<TModel>>> Import(ProgressNotification notification, string filename, TModel? originalModel)
{
return importer.Import(notification, new ImportTask(filename));
}
public abstract ArchiveDownloadRequest<T>? GetExistingDownload(T model);
private bool canDownload(T model) => GetExistingDownload(model) == null && api != null;