mirror of
https://github.com/ppy/osu.git
synced 2025-03-25 18:57:18 +08:00
Merge pull request #31805 from bdach/bss/the-actual-submission
Add initial beatmap submission support
This commit is contained in:
commit
37db539226
@ -24,7 +24,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
|
||||
CachedDependencies = new[]
|
||||
{
|
||||
(typeof(ScreenFooter), (object)footer),
|
||||
(typeof(BeatmapSubmissionSettings), new BeatmapSubmissionSettings()),
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
receptor,
|
||||
|
@ -61,6 +61,20 @@ namespace osu.Game.Database
|
||||
Configuration = new LegacySkinDecoder().Decode(skinStreamReader)
|
||||
};
|
||||
|
||||
MutateBeatmap(model, playableBeatmap);
|
||||
|
||||
// Encode to legacy format
|
||||
var stream = new MemoryStream();
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(playableBeatmap, beatmapSkin).Encode(sw);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected virtual void MutateBeatmap(BeatmapSetInfo beatmapSet, IBeatmap playableBeatmap)
|
||||
{
|
||||
// Convert beatmap elements to be compatible with legacy format
|
||||
// So we truncate time and position values to integers, and convert paths with multiple segments to Bézier curves
|
||||
|
||||
@ -145,15 +159,6 @@ namespace osu.Game.Database
|
||||
hasPath.Path.ControlPoints.Add(new PathControlPoint(position));
|
||||
}
|
||||
}
|
||||
|
||||
// Encode to legacy format
|
||||
var stream = new MemoryStream();
|
||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||
new LegacyBeatmapEncoder(playableBeatmap, beatmapSkin).Encode(sw);
|
||||
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
protected override string FileExtension => @".osz";
|
||||
|
@ -39,6 +39,31 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString SubmissionSettings => new TranslatableString(getKey(@"submission_settings"), @"Submission settings");
|
||||
|
||||
/// <summary>
|
||||
/// "Submit beatmap!"
|
||||
/// </summary>
|
||||
public static LocalisableString ConfirmSubmission => new TranslatableString(getKey(@"confirm_submission"), @"Submit beatmap!");
|
||||
|
||||
/// <summary>
|
||||
/// "Exporting beatmap for compatibility..."
|
||||
/// </summary>
|
||||
public static LocalisableString Exporting => new TranslatableString(getKey(@"exporting"), @"Exporting beatmap for compatibility...");
|
||||
|
||||
/// <summary>
|
||||
/// "Preparing for upload..."
|
||||
/// </summary>
|
||||
public static LocalisableString Preparing => new TranslatableString(getKey(@"preparing"), @"Preparing for upload...");
|
||||
|
||||
/// <summary>
|
||||
/// "Uploading beatmap contents..."
|
||||
/// </summary>
|
||||
public static LocalisableString Uploading => new TranslatableString(getKey(@"uploading"), @"Uploading beatmap contents...");
|
||||
|
||||
/// <summary>
|
||||
/// "Finishing up..."
|
||||
/// </summary>
|
||||
public static LocalisableString Finishing => new TranslatableString(getKey(@"finishing"), @"Finishing up...");
|
||||
|
||||
/// <summary>
|
||||
/// "Before you continue, we ask you to check whether the content you are uploading has been cleared for upload. Please understand that you are responsible for the content you upload to the platform and if in doubt, should ask permission from the creators before uploading!"
|
||||
/// </summary>
|
||||
@ -115,9 +140,24 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString LoadInBrowserAfterSubmission => new TranslatableString(getKey(@"load_in_browser_after_submission"), @"Load in browser after submission");
|
||||
|
||||
/// <summary>
|
||||
/// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
|
||||
/// "Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost."
|
||||
/// </summary>
|
||||
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
|
||||
public static LocalisableString LegacyExportDisclaimer => new TranslatableString(getKey(@"legacy_export_disclaimer"), @"Note: In order to make it possible for users of all osu! versions to enjoy your beatmap, it will be exported in a backwards-compatible format. While we have made efforts to ensure that process keeps the beatmap playable in its intended form, some data related to features that previous versions of osu! do not support may be lost.");
|
||||
|
||||
/// <summary>
|
||||
/// "Empty beatmaps cannot be submitted."
|
||||
/// </summary>
|
||||
public static LocalisableString EmptyBeatmapsCannotBeSubmitted => new TranslatableString(getKey(@"empty_beatmaps_cannot_be_submitted"), @"Empty beatmaps cannot be submitted.");
|
||||
|
||||
/// <summary>
|
||||
/// "Update beatmap!"
|
||||
/// </summary>
|
||||
public static LocalisableString UpdateBeatmap => new TranslatableString(getKey(@"update_beatmap"), @"Update beatmap!");
|
||||
|
||||
/// <summary>
|
||||
/// "Upload NEW beatmap!"
|
||||
/// </summary>
|
||||
public static LocalisableString UploadNewBeatmap => new TranslatableString(getKey(@"upload_new_beatmap"), @"Upload NEW beatmap!");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
|
@ -69,6 +69,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString DeleteDifficulty => new TranslatableString(getKey(@"delete_difficulty"), @"Delete difficulty");
|
||||
|
||||
/// <summary>
|
||||
/// "Edit externally"
|
||||
/// </summary>
|
||||
public static LocalisableString EditExternally => new TranslatableString(getKey(@"edit_externally"), @"Edit externally");
|
||||
|
||||
/// <summary>
|
||||
/// "Submit beatmap"
|
||||
/// </summary>
|
||||
public static LocalisableString SubmitBeatmap => new TranslatableString(getKey(@"submit_beatmap"), @"Submit beatmap");
|
||||
|
||||
/// <summary>
|
||||
/// "setup"
|
||||
/// </summary>
|
||||
|
@ -26,10 +26,8 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
public uint BeatmapSetID { get; }
|
||||
|
||||
// ReSharper disable once CollectionNeverUpdated.Global
|
||||
public Dictionary<string, byte[]> FilesChanged { get; } = new Dictionary<string, byte[]>();
|
||||
|
||||
// ReSharper disable once CollectionNeverUpdated.Global
|
||||
public HashSet<string> FilesDeleted { get; } = new HashSet<string>();
|
||||
|
||||
public PatchBeatmapPackageRequest(uint beatmapSetId)
|
||||
@ -48,7 +46,7 @@ namespace osu.Game.Online.API.Requests
|
||||
foreach (string filename in FilesDeleted)
|
||||
request.AddParameter(@"filesDeleted", filename, RequestParameterType.Form);
|
||||
|
||||
request.Timeout = 60_000;
|
||||
request.Timeout = 600_000;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Online.API.Requests
|
||||
var request = base.CreateWebRequest();
|
||||
request.AddFile(@"beatmapArchive", oszPackage);
|
||||
request.Method = HttpMethod.Put;
|
||||
request.Timeout = 60_000;
|
||||
request.Timeout = 600_000;
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Online
|
||||
SpectatorUrl = $@"{APIUrl}/signalr/spectator";
|
||||
MultiplayerUrl = $@"{APIUrl}/signalr/multiplayer";
|
||||
MetadataUrl = $@"{APIUrl}/signalr/metadata";
|
||||
BeatmapSubmissionServiceUrl = $@"{APIUrl}/beatmap-submission";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,8 @@ namespace osu.Game.Overlays
|
||||
private LoadingSpinner loading = null!;
|
||||
private ScheduledDelegate? loadingShowDelegate;
|
||||
|
||||
public bool Completed { get; private set; }
|
||||
|
||||
protected WizardOverlay(OverlayColourScheme scheme)
|
||||
: base(scheme)
|
||||
{
|
||||
@ -221,6 +223,7 @@ namespace osu.Game.Overlays
|
||||
else
|
||||
{
|
||||
CurrentStepIndex = null;
|
||||
Completed = true;
|
||||
Hide();
|
||||
}
|
||||
|
||||
|
@ -32,6 +32,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
@ -52,6 +53,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osu.Game.Screens.Edit.Design;
|
||||
using osu.Game.Screens.Edit.GameplayTest;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Edit.Submission;
|
||||
using osu.Game.Screens.Edit.Timing;
|
||||
using osu.Game.Screens.Edit.Verify;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
@ -111,6 +113,10 @@ namespace osu.Game.Screens.Edit
|
||||
[Resolved(canBeNull: true)]
|
||||
private INotificationOverlay notifications { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[CanBeNull]
|
||||
private LoginOverlay loginOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; }
|
||||
|
||||
@ -1251,11 +1257,22 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
if (RuntimeInfo.IsDesktop)
|
||||
{
|
||||
var externalEdit = new EditorMenuItem("Edit externally", MenuItemType.Standard, editExternally);
|
||||
var externalEdit = new EditorMenuItem(EditorStrings.EditExternally, MenuItemType.Standard, editExternally);
|
||||
saveRelatedMenuItems.Add(externalEdit);
|
||||
yield return externalEdit;
|
||||
}
|
||||
|
||||
bool isSetMadeOfLegacyRulesetBeatmaps = (isNewBeatmap && Ruleset.Value.IsLegacyRuleset())
|
||||
|| (!isNewBeatmap && Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Ruleset.IsLegacyRuleset()));
|
||||
bool submissionAvailable = api.Endpoints.BeatmapSubmissionServiceUrl != null;
|
||||
|
||||
if (isSetMadeOfLegacyRulesetBeatmaps && submissionAvailable)
|
||||
{
|
||||
var upload = new EditorMenuItem(EditorStrings.SubmitBeatmap, MenuItemType.Standard, submitBeatmap);
|
||||
saveRelatedMenuItems.Add(upload);
|
||||
yield return upload;
|
||||
}
|
||||
|
||||
if (editorBeatmap.BeatmapInfo.OnlineID > 0)
|
||||
{
|
||||
yield return new OsuMenuItemSpacer();
|
||||
@ -1304,6 +1321,42 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
}
|
||||
|
||||
private void submitBeatmap()
|
||||
{
|
||||
if (api.State.Value != APIState.Online)
|
||||
{
|
||||
loginOverlay?.Show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorBeatmap.HitObjects.Any())
|
||||
{
|
||||
notifications?.Post(new SimpleNotification
|
||||
{
|
||||
Text = BeatmapSubmissionStrings.EmptyBeatmapsCannotBeSubmitted,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasUnsavedChanges)
|
||||
{
|
||||
dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptMutationOperation(() =>
|
||||
{
|
||||
if (!Save())
|
||||
return false;
|
||||
|
||||
startSubmission();
|
||||
return true;
|
||||
})));
|
||||
}
|
||||
else
|
||||
{
|
||||
startSubmission();
|
||||
}
|
||||
|
||||
void startSubmission() => this.Push(new BeatmapSubmissionScreen());
|
||||
}
|
||||
|
||||
private void exportBeatmap(bool legacy)
|
||||
{
|
||||
if (HasUnsavedChanges)
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
@ -15,10 +17,14 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
AddStep<ScreenContentPermissions>();
|
||||
AddStep<ScreenFrequentlyAskedQuestions>();
|
||||
if (beatmap.Value.BeatmapSetInfo.OnlineID <= 0)
|
||||
{
|
||||
AddStep<ScreenContentPermissions>();
|
||||
AddStep<ScreenFrequentlyAskedQuestions>();
|
||||
}
|
||||
|
||||
AddStep<ScreenSubmissionSettings>();
|
||||
|
||||
Header.Title = BeatmapSubmissionStrings.BeatmapSubmissionTitle;
|
||||
|
446
osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs
Normal file
446
osu.Game/Screens/Edit/Submission/BeatmapSubmissionScreen.cs
Normal file
@ -0,0 +1,446 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Submission
|
||||
{
|
||||
public partial class BeatmapSubmissionScreen : OsuScreen
|
||||
{
|
||||
private BeatmapSubmissionOverlay overlay = null!;
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
protected override bool InitialBackButtonVisibility => false;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realmAccess { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Storage storage { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
[Cached]
|
||||
private BeatmapSubmissionSettings settings { get; } = new BeatmapSubmissionSettings();
|
||||
|
||||
private Container submissionProgress = null!;
|
||||
private SubmissionStageProgress exportStep = null!;
|
||||
private SubmissionStageProgress createSetStep = null!;
|
||||
private SubmissionStageProgress uploadStep = null!;
|
||||
private SubmissionStageProgress updateStep = null!;
|
||||
private Container successContainer = null!;
|
||||
private Container flashLayer = null!;
|
||||
|
||||
private uint? beatmapSetId;
|
||||
private MemoryStream? beatmapPackageStream;
|
||||
|
||||
private ProgressNotification? exportProgressNotification;
|
||||
private ProgressNotification? updateProgressNotification;
|
||||
|
||||
private Live<BeatmapSetInfo>? importedSet;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
overlay = new BeatmapSubmissionOverlay(),
|
||||
submissionProgress = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
AutoSizeDuration = 400,
|
||||
AutoSizeEasing = Easing.OutQuint,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.6f,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
createSetStep = new SubmissionStageProgress
|
||||
{
|
||||
StageDescription = BeatmapSubmissionStrings.Preparing,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
exportStep = new SubmissionStageProgress
|
||||
{
|
||||
StageDescription = BeatmapSubmissionStrings.Exporting,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
uploadStep = new SubmissionStageProgress
|
||||
{
|
||||
StageDescription = BeatmapSubmissionStrings.Uploading,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
updateStep = new SubmissionStageProgress
|
||||
{
|
||||
StageDescription = BeatmapSubmissionStrings.Finishing,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
successContainer = new Container
|
||||
{
|
||||
Padding = new MarginPadding(20),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Child = flashLayer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Depth = float.MinValue,
|
||||
Alpha = 0,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
overlay.State.BindValueChanged(_ =>
|
||||
{
|
||||
if (overlay.State.Value == Visibility.Hidden)
|
||||
{
|
||||
if (!overlay.Completed)
|
||||
{
|
||||
allowExit();
|
||||
this.Exit();
|
||||
}
|
||||
else
|
||||
{
|
||||
submissionProgress.FadeIn(200, Easing.OutQuint);
|
||||
createBeatmapSet();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createBeatmapSet()
|
||||
{
|
||||
bool beatmapHasOnlineId = Beatmap.Value.BeatmapSetInfo.OnlineID > 0;
|
||||
|
||||
var createRequest = beatmapHasOnlineId
|
||||
? PutBeatmapSetRequest.UpdateExisting(
|
||||
(uint)Beatmap.Value.BeatmapSetInfo.OnlineID,
|
||||
Beatmap.Value.BeatmapSetInfo.Beatmaps.Where(b => b.OnlineID > 0).Select(b => (uint)b.OnlineID).ToArray(),
|
||||
(uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count(b => b.OnlineID <= 0),
|
||||
settings.Target.Value)
|
||||
: PutBeatmapSetRequest.CreateNew((uint)Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, settings.Target.Value);
|
||||
|
||||
createRequest.Success += async response =>
|
||||
{
|
||||
createSetStep.SetCompleted();
|
||||
beatmapSetId = response.BeatmapSetId;
|
||||
|
||||
// at this point the set has an assigned online ID.
|
||||
// it's important to proactively store it to the realm database,
|
||||
// so that in the event in further failures in the process, the online ID is not lost.
|
||||
// losing it can incur creation of redundant new sets server-side, or even cause online ID confusion.
|
||||
if (!beatmapHasOnlineId)
|
||||
{
|
||||
await realmAccess.WriteAsync(r =>
|
||||
{
|
||||
var refetchedSet = r.Find<BeatmapSetInfo>(Beatmap.Value.BeatmapSetInfo.ID);
|
||||
refetchedSet!.OnlineID = (int)beatmapSetId.Value;
|
||||
}).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
await createBeatmapPackage(response).ConfigureAwait(true);
|
||||
};
|
||||
createRequest.Failure += ex =>
|
||||
{
|
||||
createSetStep.SetFailed(ex.Message);
|
||||
Logger.Log($"Beatmap set submission failed on creation: {ex}");
|
||||
allowExit();
|
||||
};
|
||||
|
||||
createSetStep.SetInProgress();
|
||||
api.Queue(createRequest);
|
||||
}
|
||||
|
||||
private async Task createBeatmapPackage(PutBeatmapSetResponse response)
|
||||
{
|
||||
Debug.Assert(ThreadSafety.IsUpdateThread);
|
||||
|
||||
exportStep.SetInProgress();
|
||||
|
||||
try
|
||||
{
|
||||
beatmapPackageStream = new MemoryStream();
|
||||
exportProgressNotification = new ProgressNotification();
|
||||
|
||||
var legacyBeatmapExporter = new SubmissionBeatmapExporter(storage, response);
|
||||
|
||||
await legacyBeatmapExporter
|
||||
.ExportToStreamAsync(Beatmap.Value.BeatmapSetInfo.ToLive(realmAccess), beatmapPackageStream, exportProgressNotification)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exportStep.SetFailed(ex.Message);
|
||||
exportProgressNotification = null;
|
||||
Logger.Log($"Beatmap set submission failed on export: {ex}");
|
||||
allowExit();
|
||||
}
|
||||
|
||||
exportStep.SetCompleted();
|
||||
exportProgressNotification = null;
|
||||
|
||||
await Task.Delay(200).ConfigureAwait(true);
|
||||
|
||||
if (response.Files.Count > 0)
|
||||
await patchBeatmapSet(response.Files).ConfigureAwait(true);
|
||||
else
|
||||
replaceBeatmapSet();
|
||||
}
|
||||
|
||||
private async Task patchBeatmapSet(ICollection<BeatmapSetFile> onlineFiles)
|
||||
{
|
||||
Debug.Assert(beatmapSetId != null);
|
||||
Debug.Assert(beatmapPackageStream != null);
|
||||
|
||||
var onlineFilesByFilename = onlineFiles.ToDictionary(f => f.Filename, f => f.SHA2Hash);
|
||||
|
||||
// disposing the `ArchiveReader` makes the underlying stream no longer readable which we don't want.
|
||||
// make a local copy to defend against it.
|
||||
using var archiveReader = new ZipArchiveReader(new MemoryStream(beatmapPackageStream.ToArray()));
|
||||
var filesToUpdate = new HashSet<string>();
|
||||
|
||||
foreach (string filename in archiveReader.Filenames)
|
||||
{
|
||||
string localHash = archiveReader.GetStream(filename).ComputeSHA2Hash();
|
||||
|
||||
if (!onlineFilesByFilename.Remove(filename, out string? onlineHash))
|
||||
{
|
||||
filesToUpdate.Add(filename);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (localHash != onlineHash)
|
||||
filesToUpdate.Add(filename);
|
||||
}
|
||||
|
||||
var changedFiles = new Dictionary<string, byte[]>();
|
||||
|
||||
foreach (string file in filesToUpdate)
|
||||
changedFiles.Add(file, await archiveReader.GetStream(file).ReadAllBytesToArrayAsync().ConfigureAwait(true));
|
||||
|
||||
var patchRequest = new PatchBeatmapPackageRequest(beatmapSetId.Value);
|
||||
patchRequest.FilesChanged.AddRange(changedFiles);
|
||||
patchRequest.FilesDeleted.AddRange(onlineFilesByFilename.Keys);
|
||||
patchRequest.Success += uploadCompleted;
|
||||
patchRequest.Failure += ex =>
|
||||
{
|
||||
uploadStep.SetFailed(ex.Message);
|
||||
Logger.Log($"Beatmap submission failed on upload: {ex}");
|
||||
allowExit();
|
||||
};
|
||||
patchRequest.Progressed += (current, total) => uploadStep.SetInProgress((float)current / total);
|
||||
|
||||
api.Queue(patchRequest);
|
||||
uploadStep.SetInProgress();
|
||||
}
|
||||
|
||||
private void replaceBeatmapSet()
|
||||
{
|
||||
Debug.Assert(beatmapSetId != null);
|
||||
Debug.Assert(beatmapPackageStream != null);
|
||||
|
||||
var uploadRequest = new ReplaceBeatmapPackageRequest(beatmapSetId.Value, beatmapPackageStream.ToArray());
|
||||
|
||||
uploadRequest.Success += uploadCompleted;
|
||||
uploadRequest.Failure += ex =>
|
||||
{
|
||||
uploadStep.SetFailed(ex.Message);
|
||||
Logger.Log($"Beatmap submission failed on upload: {ex}");
|
||||
allowExit();
|
||||
};
|
||||
uploadRequest.Progressed += (current, total) => uploadStep.SetInProgress((float)current / Math.Max(total, 1));
|
||||
|
||||
api.Queue(uploadRequest);
|
||||
uploadStep.SetInProgress();
|
||||
}
|
||||
|
||||
private void uploadCompleted()
|
||||
{
|
||||
uploadStep.SetCompleted();
|
||||
updateLocalBeatmap().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task updateLocalBeatmap()
|
||||
{
|
||||
Debug.Assert(beatmapSetId != null);
|
||||
Debug.Assert(beatmapPackageStream != null);
|
||||
|
||||
updateStep.SetInProgress();
|
||||
await Task.Delay(200).ConfigureAwait(true);
|
||||
|
||||
try
|
||||
{
|
||||
importedSet = await beatmaps.ImportAsUpdate(
|
||||
updateProgressNotification = new ProgressNotification(),
|
||||
new ImportTask(beatmapPackageStream, $"{beatmapSetId}.osz"),
|
||||
Beatmap.Value.BeatmapSetInfo).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
updateStep.SetFailed(ex.Message);
|
||||
Logger.Log($"Beatmap submission failed on local update: {ex}");
|
||||
allowExit();
|
||||
return;
|
||||
}
|
||||
|
||||
updateStep.SetCompleted();
|
||||
showBeatmapCard();
|
||||
allowExit();
|
||||
|
||||
if (configManager.Get<bool>(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission))
|
||||
{
|
||||
await Task.Delay(1000).ConfigureAwait(true);
|
||||
game?.OpenUrlExternally($"{api.Endpoints.WebsiteUrl}/beatmapsets/{beatmapSetId}");
|
||||
}
|
||||
}
|
||||
|
||||
private void showBeatmapCard()
|
||||
{
|
||||
Debug.Assert(beatmapSetId != null);
|
||||
|
||||
var getBeatmapSetRequest = new GetBeatmapSetRequest((int)beatmapSetId.Value);
|
||||
getBeatmapSetRequest.Success += beatmapSet =>
|
||||
{
|
||||
LoadComponentAsync(new BeatmapCardExtra(beatmapSet, false), loaded =>
|
||||
{
|
||||
successContainer.Add(loaded);
|
||||
flashLayer.FadeOutFromOne(2000, Easing.OutQuint);
|
||||
});
|
||||
};
|
||||
|
||||
api.Queue(getBeatmapSetRequest);
|
||||
}
|
||||
|
||||
private void allowExit()
|
||||
{
|
||||
BackButtonVisibility.Value = true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (exportProgressNotification != null && exportProgressNotification.Ongoing)
|
||||
exportStep.SetInProgress(exportProgressNotification.Progress);
|
||||
|
||||
if (updateProgressNotification != null && updateProgressNotification.Ongoing)
|
||||
updateStep.SetInProgress(updateProgressNotification.Progress);
|
||||
}
|
||||
|
||||
public override bool OnExiting(ScreenExitEvent e)
|
||||
{
|
||||
// We probably want a method of cancelling in the future…
|
||||
if (!BackButtonVisibility.Value)
|
||||
return true;
|
||||
|
||||
if (importedSet != null)
|
||||
{
|
||||
game?.PerformFromScreen(s =>
|
||||
{
|
||||
if (s is OsuScreen osuScreen)
|
||||
{
|
||||
Debug.Assert(importedSet != null);
|
||||
var targetBeatmap = importedSet.Value.Beatmaps.FirstOrDefault(b => b.DifficultyName == Beatmap.Value.BeatmapInfo.DifficultyName)
|
||||
?? importedSet.Value.Beatmaps.First();
|
||||
osuScreen.Beatmap.Value = beatmaps.GetWorkingBeatmap(targetBeatmap);
|
||||
}
|
||||
|
||||
s.Push(new EditorLoader());
|
||||
}, [typeof(SongSelect)]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
||||
overlay.Show();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
beatmapPackageStream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Game.Online.API.Requests;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Submission
|
||||
{
|
||||
public class BeatmapSubmissionSettings
|
||||
{
|
||||
public Bindable<BeatmapSubmissionTarget> Target { get; } = new Bindable<BeatmapSubmissionTarget>();
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
@ -22,8 +23,10 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
private readonly BindableBool notifyOnDiscussionReplies = new BindableBool();
|
||||
private readonly BindableBool loadInBrowserAfterSubmission = new BindableBool();
|
||||
|
||||
public override LocalisableString? NextStepText => BeatmapSubmissionStrings.ConfirmSubmission;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager configManager, OsuColour colours)
|
||||
private void load(OsuConfigManager configManager, OsuColour colours, BeatmapSubmissionSettings settings)
|
||||
{
|
||||
configManager.BindWith(OsuSetting.EditorSubmissionNotifyOnDiscussionReplies, notifyOnDiscussionReplies);
|
||||
configManager.BindWith(OsuSetting.EditorSubmissionLoadInBrowserAfterSubmission, loadInBrowserAfterSubmission);
|
||||
@ -39,6 +42,7 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Caption = BeatmapSubmissionStrings.BeatmapSubmissionTargetCaption,
|
||||
Current = settings.Target,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
@ -60,14 +64,5 @@ namespace osu.Game.Screens.Edit.Submission
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private enum BeatmapSubmissionTarget
|
||||
{
|
||||
[LocalisableDescription(typeof(BeatmapSubmissionStrings), nameof(BeatmapSubmissionStrings.BeatmapSubmissionTargetWIP))]
|
||||
WIP,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapSubmissionStrings), nameof(BeatmapSubmissionStrings.BeatmapSubmissionTargetPending))]
|
||||
Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,53 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Submission
|
||||
{
|
||||
public class SubmissionBeatmapExporter : LegacyBeatmapExporter
|
||||
{
|
||||
private readonly uint? beatmapSetId;
|
||||
private readonly HashSet<int>? allocatedBeatmapIds;
|
||||
|
||||
public SubmissionBeatmapExporter(Storage storage, PutBeatmapSetResponse putBeatmapSetResponse)
|
||||
: base(storage)
|
||||
{
|
||||
beatmapSetId = putBeatmapSetResponse.BeatmapSetId;
|
||||
allocatedBeatmapIds = putBeatmapSetResponse.BeatmapIds.Select(id => (int)id).ToHashSet();
|
||||
}
|
||||
|
||||
protected override void MutateBeatmap(BeatmapSetInfo beatmapSet, IBeatmap playableBeatmap)
|
||||
{
|
||||
base.MutateBeatmap(beatmapSet, playableBeatmap);
|
||||
|
||||
if (beatmapSetId != null && allocatedBeatmapIds != null)
|
||||
{
|
||||
playableBeatmap.BeatmapInfo.BeatmapSet = beatmapSet;
|
||||
playableBeatmap.BeatmapInfo.BeatmapSet!.OnlineID = (int)beatmapSetId;
|
||||
|
||||
if (allocatedBeatmapIds.Contains(playableBeatmap.BeatmapInfo.OnlineID))
|
||||
{
|
||||
allocatedBeatmapIds.Remove(playableBeatmap.BeatmapInfo.OnlineID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playableBeatmap.BeatmapInfo.OnlineID > 0)
|
||||
throw new InvalidOperationException(@"Encountered beatmap with ID that has not been assigned to it by the server!");
|
||||
|
||||
if (allocatedBeatmapIds.Count == 0)
|
||||
throw new InvalidOperationException(@"Ran out of new beatmap IDs to assign to unsubmitted beatmaps!");
|
||||
|
||||
int newId = allocatedBeatmapIds.First();
|
||||
allocatedBeatmapIds.Remove(newId);
|
||||
playableBeatmap.BeatmapInfo.OnlineID = newId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user