mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 13:37:25 +08:00
Add basic flow for mounting beatmaps for external editing
This commit is contained in:
parent
95783bd6c2
commit
d3c66e2404
@ -415,6 +415,9 @@ namespace osu.Game.Beatmaps
|
|||||||
public Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) =>
|
public Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) =>
|
||||||
beatmapImporter.ImportAsUpdate(notification, importTask, original);
|
beatmapImporter.ImportAsUpdate(notification, importTask, original);
|
||||||
|
|
||||||
|
public Task<ExternalEditOperation<BeatmapSetInfo>> BeginExternalEditing(BeatmapSetInfo model) =>
|
||||||
|
beatmapImporter.BeginExternalEditing(model);
|
||||||
|
|
||||||
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
public Task Export(BeatmapSetInfo beatmap) => beatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
||||||
|
|
||||||
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
public Task ExportLegacy(BeatmapSetInfo beatmap) => legacyBeatmapExporter.ExportAsync(beatmap.ToLive(Realm));
|
||||||
|
48
osu.Game/Database/ExternalEditOperation.cs
Normal file
48
osu.Game/Database/ExternalEditOperation.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class ExternalEditOperation<TModel> where TModel : class, IHasGuidPrimaryKey
|
||||||
|
{
|
||||||
|
public readonly string MountedPath;
|
||||||
|
|
||||||
|
private readonly IModelImporter<TModel> importer;
|
||||||
|
private readonly TModel original;
|
||||||
|
|
||||||
|
private bool isMounted;
|
||||||
|
|
||||||
|
public ExternalEditOperation(IModelImporter<TModel> importer, TModel original, string path)
|
||||||
|
{
|
||||||
|
this.importer = importer;
|
||||||
|
this.original = original;
|
||||||
|
|
||||||
|
MountedPath = path;
|
||||||
|
|
||||||
|
isMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Live<TModel>?> Finish()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(MountedPath) || !isMounted)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Live<TModel>? imported = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(MountedPath), original)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.Delete(MountedPath, true);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
isMounted = false;
|
||||||
|
|
||||||
|
return imported;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,12 @@ namespace osu.Game.Database
|
|||||||
/// <returns>The imported model.</returns>
|
/// <returns>The imported model.</returns>
|
||||||
Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original);
|
Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mount all files for a <see cref="TModel"/> to a temporary directory to allow for external editing.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The <see cref="TModel"/> to mount.</param>
|
||||||
|
public Task<ExternalEditOperation<TModel>> BeginExternalEditing(TModel model);
|
||||||
|
|
||||||
/// <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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -179,6 +179,30 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public virtual Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException();
|
public virtual Task<Live<TModel>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public async Task<ExternalEditOperation<TModel>> BeginExternalEditing(TModel model)
|
||||||
|
{
|
||||||
|
string mountedPath = Path.Join(Path.GetTempPath(), model.Hash);
|
||||||
|
|
||||||
|
if (Directory.Exists(mountedPath))
|
||||||
|
Directory.Delete(mountedPath, true);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(mountedPath);
|
||||||
|
|
||||||
|
foreach (var realmFile in model.Files)
|
||||||
|
{
|
||||||
|
string sourcePath = Files.Storage.GetFullPath(realmFile.File.GetStoragePath());
|
||||||
|
string destinationPath = Path.Join(mountedPath, realmFile.Filename);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
|
||||||
|
|
||||||
|
using (var inStream = Files.Storage.GetStream(sourcePath))
|
||||||
|
using (var outStream = File.Create(destinationPath))
|
||||||
|
await inStream.CopyToAsync(outStream).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExternalEditOperation<TModel>(this, model, mountedPath);
|
||||||
|
}
|
||||||
|
|
||||||
/// <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.
|
||||||
/// Note that this bypasses the UI flow and should only be used for special cases or testing.
|
/// Note that this bypasses the UI flow and should only be used for special cases or testing.
|
||||||
|
@ -15,10 +15,10 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Scoring.Legacy;
|
using osu.Game.Scoring.Legacy;
|
||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
@ -214,6 +214,7 @@ namespace osu.Game.Scoring
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task<Live<ScoreInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
public Task<Live<ScoreInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original);
|
||||||
|
public Task<ExternalEditOperation<ScoreInfo>> BeginExternalEditing(ScoreInfo model) => scoreImporter.BeginExternalEditing(model);
|
||||||
|
|
||||||
public Live<ScoreInfo>? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
public Live<ScoreInfo>? Import(ScoreInfo item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
||||||
scoreImporter.ImportModel(item, archive, parameters, cancellationToken);
|
scoreImporter.ImportModel(item, archive, parameters, cancellationToken);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
@ -13,6 +14,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -22,6 +24,7 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@ -820,6 +823,9 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
resetTrack();
|
resetTrack();
|
||||||
|
|
||||||
|
fileMountOperation?.Dispose();
|
||||||
|
fileMountOperation = null;
|
||||||
|
|
||||||
refetchBeatmap();
|
refetchBeatmap();
|
||||||
|
|
||||||
return base.OnExiting(e);
|
return base.OnExiting(e);
|
||||||
@ -1095,6 +1101,11 @@ namespace osu.Game.Screens.Edit
|
|||||||
lastSavedHash = changeHandler?.CurrentStateHash;
|
lastSavedHash = changeHandler?.CurrentStateHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EditorMenuItem mountFilesItem;
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
|
private Task<ExternalEditOperation<BeatmapSetInfo>> fileMountOperation;
|
||||||
|
|
||||||
private IEnumerable<MenuItem> createFileMenuItems()
|
private IEnumerable<MenuItem> createFileMenuItems()
|
||||||
{
|
{
|
||||||
yield return createDifficultyCreationMenu();
|
yield return createDifficultyCreationMenu();
|
||||||
@ -1112,12 +1123,44 @@ namespace osu.Game.Screens.Edit
|
|||||||
var export = createExportMenu();
|
var export = createExportMenu();
|
||||||
saveRelatedMenuItems.AddRange(export.Items);
|
saveRelatedMenuItems.AddRange(export.Items);
|
||||||
yield return export;
|
yield return export;
|
||||||
|
|
||||||
|
yield return mountFilesItem = new EditorMenuItem("Mount files", MenuItemType.Standard, mountFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new OsuMenuItemSpacer();
|
yield return new OsuMenuItemSpacer();
|
||||||
yield return new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit);
|
yield return new EditorMenuItem(CommonStrings.Exit, MenuItemType.Standard, this.Exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost gameHost { get; set; }
|
||||||
|
|
||||||
|
private void mountFiles()
|
||||||
|
{
|
||||||
|
if (fileMountOperation == null)
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
|
||||||
|
fileMountOperation = beatmapManager.BeginExternalEditing(editorBeatmap.BeatmapInfo.BeatmapSet!);
|
||||||
|
mountFilesItem.Text.Value = "Dismount files";
|
||||||
|
|
||||||
|
fileMountOperation.ContinueWith(t =>
|
||||||
|
{
|
||||||
|
var operation = t.GetResultSafely();
|
||||||
|
|
||||||
|
// Ensure the trailing separator is present in order to show the folder contents.
|
||||||
|
gameHost.OpenFileExternally(operation.MountedPath.TrimDirectorySeparator() + Path.DirectorySeparatorChar);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fileMountOperation.GetResultSafely().Finish().ContinueWith(t => Schedule(() =>
|
||||||
|
{
|
||||||
|
fileMountOperation = null;
|
||||||
|
SwitchToDifficulty(t.GetResultSafely().Value.Detach().Beatmaps.First());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private EditorMenuItem createExportMenu()
|
private EditorMenuItem createExportMenu()
|
||||||
{
|
{
|
||||||
var exportItems = new List<MenuItem>
|
var exportItems = new List<MenuItem>
|
||||||
|
@ -312,6 +312,8 @@ namespace osu.Game.Skinning
|
|||||||
public Task<Live<SkinInfo>> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) =>
|
public Task<Live<SkinInfo>> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) =>
|
||||||
skinImporter.ImportAsUpdate(notification, task, original);
|
skinImporter.ImportAsUpdate(notification, task, original);
|
||||||
|
|
||||||
|
public Task<ExternalEditOperation<SkinInfo>> BeginExternalEditing(SkinInfo model) => skinImporter.BeginExternalEditing(model);
|
||||||
|
|
||||||
public Task<Live<SkinInfo>> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
public Task<Live<SkinInfo>> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) =>
|
||||||
skinImporter.Import(task, parameters, cancellationToken);
|
skinImporter.Import(task, parameters, cancellationToken);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user