diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index c4dde59e3f..703c63b91a 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -14,7 +14,6 @@ using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Overlays.Notifications; using osu.Game.Skinning; using SharpCompress.Archives.Zip; @@ -122,7 +121,7 @@ namespace osu.Game.Tests.Skins.IO await import1.PerformRead(async s => { - await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get(), new ProgressNotification(), exportStream).ExportAsync(s); + await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get()).ExportToStreamAsync(s, exportStream); }); string exportFilename = import1.GetDisplayString(); @@ -204,7 +203,7 @@ namespace osu.Game.Tests.Skins.IO Assert.IsFalse(s.Protected); Assert.AreEqual(typeof(ArgonSkin), s.CreateInstance(skinManager).GetType()); - await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get(), new ProgressNotification(), exportStream).ExportAsync(s); + await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get()).ExportToStreamAsync(s, exportStream); Assert.Greater(exportStream.Length, 0); }); @@ -237,7 +236,7 @@ namespace osu.Game.Tests.Skins.IO Assert.IsFalse(s.Protected); Assert.AreEqual(typeof(DefaultLegacySkin), s.CreateInstance(skinManager).GetType()); - await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get(), new ProgressNotification(), exportStream).ExportAsync(s); + await new LegacySkinExporter(osu.Dependencies.Get(), osu.Dependencies.Get()).ExportToStreamAsync(s, exportStream); Assert.Greater(exportStream.Length, 0); }); diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index 5505d141ee..22bccbcda6 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -1,20 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.IO; using osu.Framework.Platform; using osu.Game.Beatmaps; -using osu.Game.Overlays.Notifications; +using osu.Game.Overlays; namespace osu.Game.Database { public class LegacyBeatmapExporter : LegacyModelExporter { - protected override string FileExtension => ".osz"; - - public LegacyBeatmapExporter(Storage storage, RealmAccess realm, ProgressNotification notification, Stream? stream = null) - : base(storage, realm, notification, stream) + public LegacyBeatmapExporter(Storage storage, RealmAccess realm, INotificationOverlay? notifications = null) + : base(storage, realm, notifications) { } + + protected override string FileExtension => ".osz"; } } diff --git a/osu.Game/Database/LegacyExportManager.cs b/osu.Game/Database/LegacyExportManager.cs deleted file mode 100644 index a694a0705e..0000000000 --- a/osu.Game/Database/LegacyExportManager.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Platform; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; -using osu.Game.Scoring; -using osu.Game.Skinning; - -namespace osu.Game.Database -{ - /// - /// A class which centrally manage legacy file exports. - /// - [ExcludeFromDynamicCompile] - public class LegacyExportManager : Component - { - [Resolved] - private RealmAccess realmAccess { get; set; } = null!; - - [Resolved] - private Storage exportStorage { get; set; } = null!; - - [Resolved] - private INotificationOverlay? notifications { get; set; } - - /// - /// Identify the model type and and automatically assigned to the corresponding exporter. - /// - /// The model should export. - /// The stream if requires a specific output-stream - /// - public async Task ExportAsync(IHasGuidPrimaryKey item, Stream? stream = null) - { - var notification = new ProgressNotification - { - State = ProgressNotificationState.Active, - Text = "Exporting...", - CompletionText = "Export completed" - }; - notifications?.Post(notification); - - switch (item) - { - case SkinInfo: - await new LegacySkinExporter(exportStorage, realmAccess, notification, stream).ExportAsync(item); - break; - - case ScoreInfo: - await new LegacyScoreExporter(exportStorage, realmAccess, notification, stream).ExportASync(item); - break; - - case BeatmapSetInfo: - await new LegacyBeatmapExporter(exportStorage, realmAccess, notification, stream).ExportAsync(item); - break; - } - } - } -} diff --git a/osu.Game/Database/LegacyModelExporter.cs b/osu.Game/Database/LegacyModelExporter.cs index 01ebbdcaff..83167e7319 100644 --- a/osu.Game/Database/LegacyModelExporter.cs +++ b/osu.Game/Database/LegacyModelExporter.cs @@ -5,8 +5,10 @@ using System; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Extensions; +using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using Realms; using SharpCompress.Archives.Zip; @@ -16,112 +18,106 @@ namespace osu.Game.Database /// /// A class which handles exporting legacy user data of a single type from osu-stable. /// - public abstract class LegacyModelExporter - where TModel : RealmObject + public abstract class LegacyModelExporter : Component + where TModel : RealmObject, IHasNamedFiles, IHasGuidPrimaryKey { /// /// The file extension for exports (including the leading '.'). /// protected abstract string FileExtension { get; } - protected readonly Storage UserFileStorage; + protected Storage UserFileStorage; + protected Storage ExportStorage; - protected readonly Storage ExportStorage; + protected RealmAccess RealmAccess; - protected readonly RealmAccess RealmAccess; - - protected readonly ProgressNotification Notification; + private readonly ProgressNotification notification; protected string Filename = null!; - protected Stream? OutputStream; + private bool canCancel = true; - protected bool ShouldDisposeStream; - - protected LegacyModelExporter(Storage storage, RealmAccess realm, ProgressNotification notification, Stream? stream = null) + protected LegacyModelExporter(Storage storage, RealmAccess realm, INotificationOverlay? notifications = null) { ExportStorage = storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); - Notification = notification; RealmAccess = realm; - OutputStream = stream; - ShouldDisposeStream = false; + + notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Exporting...", + CompletionText = "Export completed" + }; + notification.CancelRequested += () => canCancel; + + notifications?.Post(notification); } - /// - /// Export model to - /// if is null, model will export to default folder. - /// - /// The model which have Guid. - /// + public async Task ExportAsync(RealmObject item) { - bool canCancel = true; - Notification.CancelRequested += () => canCancel; + if (item is TModel model) + { + Filename = $"{model.GetDisplayString().GetValidFilename()}{FileExtension}"; - public virtual async Task ExportAsync(IHasGuidPrimaryKey uuid) + using (var stream = ExportStorage.CreateFileSafely(Filename)) + { + await ExportToStreamAsync(model, stream); + } + } + } + + public virtual async Task ExportToStreamAsync(TModel uuid, Stream stream) + { Guid id = uuid.ID; await Task.Run(() => { RealmAccess.Run(r => { - if (r.Find(id) is IHasNamedFiles model) - { - Filename = $"{model.GetDisplayString().GetValidFilename()}{FileExtension}"; - } - else - { - return; - } - - if (OutputStream == null) - { - OutputStream = ExportStorage.CreateFileSafely(Filename); - ShouldDisposeStream = true; - } - - using (var archive = ZipArchive.Create()) - { - float i = 0; - - foreach (var file in model.Files) - { - if (Notification.CancellationToken.IsCancellationRequested) return; - - archive.AddEntry(file.Filename, UserFileStorage.GetStream(file.File.GetStoragePath())); - i++; - Notification.Progress = i / model.Files.Count(); - Notification.Text = $"Exporting... ({i}/{model.Files.Count()})"; - } - - Notification.Text = "Saving Zip Archive..."; - canCancel = false; - archive.SaveTo(OutputStream); - } + TModel model = r.Find(id); + createZipArchive(model, stream); }); }).ContinueWith(OnComplete); } + private void createZipArchive(TModel model, Stream outputStream) + { + using (var archive = ZipArchive.Create()) + { + float i = 0; + + foreach (var file in model.Files) + { + if (notification.CancellationToken.IsCancellationRequested) return; + + archive.AddEntry(file.Filename, UserFileStorage.GetStream(file.File.GetStoragePath())); + i++; + notification.Progress = i / model.Files.Count(); + notification.Text = $"Exporting... ({i}/{model.Files.Count()})"; + } + + notification.Text = "Saving Zip Archive..."; + canCancel = false; + archive.SaveTo(outputStream); + } + } + protected void OnComplete(Task t) { - if (ShouldDisposeStream) - { - OutputStream?.Dispose(); - } - if (t.IsFaulted) { - Notification.State = ProgressNotificationState.Cancelled; + notification.State = ProgressNotificationState.Cancelled; return; } - if (Notification.CancellationToken.IsCancellationRequested) + if (notification.CancellationToken.IsCancellationRequested) { return; } - Notification.CompletionText = "Export Complete, Click to open the folder"; - Notification.CompletionClickAction += () => ExportStorage.PresentFileExternally(Filename); - Notification.State = ProgressNotificationState.Completed; + notification.CompletionText = "Export Complete, Click to open the folder"; + notification.CompletionClickAction += () => ExportStorage.PresentFileExternally(Filename); + notification.State = ProgressNotificationState.Completed; } } } diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs index 3004c02978..e00bc2a0ca 100644 --- a/osu.Game/Database/LegacyScoreExporter.cs +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -6,49 +6,36 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Extensions; -using osu.Game.Overlays.Notifications; +using osu.Game.Overlays; using osu.Game.Scoring; namespace osu.Game.Database { public class LegacyScoreExporter : LegacyModelExporter { - protected override string FileExtension => ".osr"; - - public LegacyScoreExporter(Storage storage, RealmAccess realm, ProgressNotification notification, Stream? stream = null) - : base(storage, realm, notification, stream) + public LegacyScoreExporter(Storage storage, RealmAccess realm, INotificationOverlay? notifications = null) + : base(storage, realm, notifications) { } - public override async Task ExportASync(IHasGuidPrimaryKey uuid) + protected override string FileExtension => ".osr"; + + public override async Task ExportToStreamAsync(ScoreInfo uuid, Stream stream) { await Task.Run(() => { RealmAccess.Run(r => { - if (r.Find(uuid.ID) is IHasNamedFiles model) - { - Filename = $"{model.GetDisplayString().GetValidFilename()}{FileExtension}"; - } - else - { - return; - } + ScoreInfo model = r.Find(uuid.ID); + + Filename = $"{model.GetDisplayString().GetValidFilename()}{FileExtension}"; var file = model.Files.SingleOrDefault(); if (file == null) return; - if (Notification.CancellationToken.IsCancellationRequested) return; - - if (OutputStream == null) - { - OutputStream = ExportStorage.CreateFileSafely(Filename); - ShouldDisposeStream = true; - } - using (var inputStream = UserFileStorage.GetStream(file.File.GetStoragePath())) - inputStream.CopyTo(OutputStream); + inputStream.CopyTo(stream); }); }).ContinueWith(OnComplete); } diff --git a/osu.Game/Database/LegacySkinExporter.cs b/osu.Game/Database/LegacySkinExporter.cs index 4168763324..6e65963f29 100644 --- a/osu.Game/Database/LegacySkinExporter.cs +++ b/osu.Game/Database/LegacySkinExporter.cs @@ -1,20 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.IO; using osu.Framework.Platform; -using osu.Game.Overlays.Notifications; +using osu.Game.Overlays; using osu.Game.Skinning; namespace osu.Game.Database { public class LegacySkinExporter : LegacyModelExporter { - protected override string FileExtension => ".osk"; - - public LegacySkinExporter(Storage storage, RealmAccess realm, ProgressNotification notification, Stream? stream = null) - : base(storage, realm, notification, stream) + public LegacySkinExporter(Storage storage, RealmAccess realm, INotificationOverlay? notifications = null) + : base(storage, realm, notifications) { } + + protected override string FileExtension => ".osk"; } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index b374736648..0a9f2a81bd 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -34,6 +34,7 @@ using osuTK.Graphics; using osu.Game.Online.API; using osu.Game.Resources.Localisation.Web; using osu.Game.Utils; +using osu.Framework.Platform; namespace osu.Game.Online.Leaderboards { @@ -72,8 +73,14 @@ namespace osu.Game.Online.Leaderboards [Resolved(canBeNull: true)] private SongSelect songSelect { get; set; } - [Resolved(canBeNull: true)] - private LegacyExportManager exporter { get; set; } + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private INotificationOverlay notifications { get; set; } public ITooltip GetCustomTooltip() => new LeaderboardScoreTooltip(); public virtual ScoreInfo TooltipContent => Score; @@ -427,7 +434,7 @@ namespace osu.Game.Online.Leaderboards if (Score.Files.Count > 0) { - items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => Task.Run(() => exporter?.ExportAsync(Score)))); + items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => Task.Run(() => new LegacyScoreExporter(storage, realm, notifications).ExportAsync(Score)))); items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score)))); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 09647f9d1e..a93c187e53 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -126,9 +126,6 @@ namespace osu.Game [Cached] private readonly LegacyImportManager legacyImportManager = new LegacyImportManager(); - [Cached] - private readonly LegacyExportManager legacyExportManager = new LegacyExportManager(); - [Cached] private readonly ScreenshotManager screenshotManager = new ScreenshotManager(); @@ -871,7 +868,6 @@ namespace osu.Game }), rightFloatingOverlayContent.Add, true); loadComponentSingleFile(legacyImportManager, Add); - loadComponentSingleFile(legacyExportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index e5a26c19fc..46e760283d 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; @@ -137,8 +138,14 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } - [Resolved(canBeNull: true)] - private LegacyExportManager exporter { get; set; } + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + + [Resolved] + private INotificationOverlay notifications { get; set; } private Bindable currentSkin; @@ -161,7 +168,7 @@ namespace osu.Game.Overlays.Settings.Sections { try { - currentSkin.Value.SkinInfo.PerformRead(s => exporter?.ExportAsync(s)); + currentSkin.Value.SkinInfo.PerformRead(s => new LegacySkinExporter(storage, realm, notifications).ExportAsync(s)); } catch (Exception e) { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 77f14f689a..e53c550a1a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -21,6 +21,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Threading; @@ -90,6 +91,12 @@ namespace osu.Game.Screens.Edit [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private RealmAccess realm { get; set; } + [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } @@ -179,9 +186,6 @@ namespace osu.Game.Screens.Edit private Bindable editorBackgroundDim; private Bindable editorHitMarkers; - [Resolved(canBeNull: true)] - private LegacyExportManager exporter { get; set; } - public Editor(EditorLoader loader = null) { this.loader = loader; @@ -938,7 +942,7 @@ namespace osu.Game.Screens.Edit private void exportBeatmap() { Save(); - Task.Run(() => exporter?.ExportAsync(Beatmap.Value.BeatmapSetInfo)); + Task.Run(() => new LegacyBeatmapExporter(storage, realm, notifications).ExportAsync(Beatmap.Value.BeatmapSetInfo)); } ///