From c3eab1c4a47b89818aaed8531469b21dd48b9a0f Mon Sep 17 00:00:00 2001 From: Piotrekol <4990365+Piotrekol@users.noreply.github.com> Date: Mon, 18 Jul 2022 18:49:14 +0200 Subject: [PATCH] Add: Ability to export map sets back to .osz archives --- App/BeatmapListingActionsHandler.cs | 81 ++++++++++++ App/OsuDownloadManager.cs | 4 +- CollectionManager.sln | 6 +- Common/BeatmapListingAction.cs | 1 + Common/Interfaces/Forms/IProgressForm.cs | 9 ++ Common/Interfaces/IUserDialogs.cs | 2 + .../Controls/BeatmapListingView.Designer.cs | 24 +++- GuiComponents/Controls/BeatmapListingView.cs | 2 + GuiComponents/ProgressForm.Designer.cs | 87 +++++++++++++ GuiComponents/ProgressForm.cs | 44 +++++++ GuiComponents/ProgressForm.resx | 120 ++++++++++++++++++ GuiComponents/UserDialogs.cs | 6 + 12 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 Common/Interfaces/Forms/IProgressForm.cs create mode 100644 GuiComponents/ProgressForm.Designer.cs create mode 100644 GuiComponents/ProgressForm.cs create mode 100644 GuiComponents/ProgressForm.resx diff --git a/App/BeatmapListingActionsHandler.cs b/App/BeatmapListingActionsHandler.cs index 958f99e..858817d 100644 --- a/App/BeatmapListingActionsHandler.cs +++ b/App/BeatmapListingActionsHandler.cs @@ -9,10 +9,16 @@ using CollectionManagerExtensionsDll.Utils; using Common; using GuiComponents.Interfaces; +using SharpCompress.Archives.Zip; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; +using System.IO; +using System.IO.Compression; using System.Linq; +using System.Text; +using System.Threading; namespace App { @@ -42,6 +48,7 @@ public BeatmapListingActionsHandler(ICollectionEditor collectionEditor, IUserDia {BeatmapListingAction.OpenBeatmapPages, OpenBeatmapPages }, {BeatmapListingAction.OpenBeatmapFolder, OpenBeatmapFolder }, {BeatmapListingAction.PullWholeMapSet, PullWholeMapsets }, + {BeatmapListingAction.ExportBeatmapSets, ExportBeatmapSets }, }; } @@ -60,6 +67,80 @@ private void BeatmapListingModel_BeatmapOperation(object sender, BeatmapListingA { _beatmapOperationHandlers[args](sender); } + + private void ExportBeatmapSets(object sender) + { + var model = (IBeatmapListingModel)sender; + if (model.SelectedBeatmaps?.Count == 0) + { + _userDialogs.OkMessageBox("No beatmaps selected", "Info"); + return; + } + + var saveDirectory = _userDialogs.SelectDirectory("Select directory for exported maps", true); + var beatmapSets = model.SelectedBeatmaps.Select(b => (Beatmap: b, FileName: OsuDownloadManager.CreateOszFileName(b))) + .GroupBy(e => e.FileName).Select(e => e.First()).ToList(); + if (!Directory.Exists(saveDirectory)) + return; + + var backgroundWorker = new BackgroundWorker(); + var stringProgress = new Progress(); + var percentageProgress = new Progress(); + using var cancelationTokenSource = new CancellationTokenSource(); + var progressForm = _userDialogs.ProgressForm(stringProgress, percentageProgress); + progressForm.AbortClicked += (_, __) => cancelationTokenSource.Cancel(); + backgroundWorker.DoWork += (_, eventArgs) => + { + var cancellationToken = cancelationTokenSource.Token; + var totalCount = beatmapSets.Count(); + var stringProgressReporter = (IProgress)stringProgress; + var percentageProgressReporter = (IProgress)percentageProgress; + var failedMapSets = new StringBuilder(); + var failedMapSetsCount = 0; + for (int i = 0; i < totalCount; i++) + { + var beatmapset = beatmapSets[i]; + if (cancellationToken.IsCancellationRequested) + { + progressForm.Close(); + return; + } + + stringProgressReporter.Report($"Processing map set {i + 1} of {totalCount}.{Environment.NewLine}\"{beatmapset.FileName}\""); + var fileSaveLocation = Path.Combine(saveDirectory, beatmapset.FileName); + if (File.Exists(fileSaveLocation)) + { + percentageProgressReporter.Report(Convert.ToInt32((double)(i + 1) / totalCount * 100)); + continue; + } + + var beatmapDirectory = beatmapset.Beatmap.BeatmapDirectory(); + try + { + ZipFile.CreateFromDirectory(beatmapDirectory, fileSaveLocation); + } + catch (Exception e) + { + failedMapSets.AppendFormat("failed processing map set located at \"{0}\" with exception:{2}{1}{2}{2}", beatmapDirectory, e, Environment.NewLine); + failedMapSetsCount++; + } + + percentageProgressReporter.Report(Convert.ToInt32((double)(i + 1) / totalCount * 100)); + } + + if (failedMapSetsCount > 0) + { + File.WriteAllText(Path.Combine(saveDirectory, "log.txt"), failedMapSets.ToString()); + stringProgressReporter.Report($"Processed {totalCount - failedMapSetsCount} of {totalCount} map sets.{Environment.NewLine}{failedMapSetsCount} map sets failed to save, see full log in log.txt file in export directory"); + } + else + stringProgressReporter.Report($"Processed {totalCount} map sets without issues."); + }; + + backgroundWorker.RunWorkerAsync(); + progressForm.ShowAndBlock(); + } + private void PullWholeMapsets(object sender) { var model = (IBeatmapListingModel)sender; diff --git a/App/OsuDownloadManager.cs b/App/OsuDownloadManager.cs index 4ef2841..70060b0 100644 --- a/App/OsuDownloadManager.cs +++ b/App/OsuDownloadManager.cs @@ -156,7 +156,7 @@ private DownloadItem GetDownloadItem(Beatmap beatmap) if (beatmap.MapSetId < 1 || ListedMapSetIds.Contains(beatmap.MapSetId)) return null; long currentId = ++_downloadId; - var oszFileName = CreateFileName(beatmap); + var oszFileName = CreateOszFileName(beatmap); var downloadUrl = string.Format(SelectedDownloadSource.BaseDownloadUrl, beatmap.MapSetId) + (DownloadWithVideo != null && DownloadWithVideo.Value ? string.Empty : "?noVideo=1"); var downloadItem = _mapDownloader.DownloadFileAsync(downloadUrl, oszFileName, string.Format(SelectedDownloadSource.Referer, beatmap.MapSetId), currentId,SelectedDownloadSource.RequestTimeout); @@ -164,7 +164,7 @@ private DownloadItem GetDownloadItem(Beatmap beatmap) return downloadItem; } - private string CreateFileName(Beatmap map) + public static string CreateOszFileName(Beatmap map) { var filename = map.MapSetId + " " + map.ArtistRoman + " - " + map.TitleRoman; return Helpers.StripInvalidFileNameCharacters(filename, "_") + ".osz"; diff --git a/CollectionManager.sln b/CollectionManager.sln index 03fdcc3..0f5d185 100644 --- a/CollectionManager.sln +++ b/CollectionManager.sln @@ -1,7 +1,7 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28803.452 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CollectionManagerDll", "CollectionManagerDll\CollectionManagerDll.csproj", "{533AB47A-D1B5-45DB-A37E-F053FA3699C4}" EndProject diff --git a/Common/BeatmapListingAction.cs b/Common/BeatmapListingAction.cs index 533a6e0..fc1c796 100644 --- a/Common/BeatmapListingAction.cs +++ b/Common/BeatmapListingAction.cs @@ -10,5 +10,6 @@ public enum BeatmapListingAction CopyBeatmapsAsText, //Copy text representation of selected beatmaps CopyBeatmapsAsUrls, //Copy map links of selected beatmaps PullWholeMapSet, //Finds all mapsets from selected maps and adds them to current collection + ExportBeatmapSets, //Compress selected mapsets back to .osz archives } } \ No newline at end of file diff --git a/Common/Interfaces/Forms/IProgressForm.cs b/Common/Interfaces/Forms/IProgressForm.cs new file mode 100644 index 0000000..cc1864d --- /dev/null +++ b/Common/Interfaces/Forms/IProgressForm.cs @@ -0,0 +1,9 @@ +using System; + +namespace GuiComponents.Interfaces +{ + public interface IProgressForm : IForm + { + event EventHandler AbortClicked; + } +} \ No newline at end of file diff --git a/Common/Interfaces/IUserDialogs.cs b/Common/Interfaces/IUserDialogs.cs index 290e88f..85acef9 100644 --- a/Common/Interfaces/IUserDialogs.cs +++ b/Common/Interfaces/IUserDialogs.cs @@ -1,4 +1,5 @@ using Common; +using System; namespace GuiComponents.Interfaces { @@ -11,6 +12,7 @@ public interface IUserDialogs string SaveFile(string title, string types = "all|*.*"); bool YesNoMessageBox(string text, string caption, MessageBoxType messageBoxType = MessageBoxType.Info); (bool Result, bool doNotAskAgain) YesNoMessageBox(string text, string caption, MessageBoxType messageBoxType = MessageBoxType.Info,string doNotAskAgainText = null); + IProgressForm ProgressForm(Progress userProgressMessage, Progress completionPercentage); void OkMessageBox(string text, string caption, MessageBoxType messageBoxType = MessageBoxType.Info); } } \ No newline at end of file diff --git a/GuiComponents/Controls/BeatmapListingView.Designer.cs b/GuiComponents/Controls/BeatmapListingView.Designer.cs index 973aa76..972226e 100644 --- a/GuiComponents/Controls/BeatmapListingView.Designer.cs +++ b/GuiComponents/Controls/BeatmapListingView.Designer.cs @@ -69,6 +69,7 @@ private void InitializeComponent() this.olvColumn9 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn())); this.olvColumn11 = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn())); this.MainBpm = ((BrightIdeasSoftware.OLVColumn)(new BrightIdeasSoftware.OLVColumn())); + this.exportBeatmapSetsMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.BeatmapsContextMenuStrip.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ListViewBeatmaps)).BeginInit(); this.SuspendLayout(); @@ -108,9 +109,10 @@ private void InitializeComponent() this.DeleteMapMenuStrip, this.searchToolStripMenuItem, this.copyToolStripMenuItem, - this.PullMapsetMenuStrip}); + this.PullMapsetMenuStrip, + this.exportBeatmapSetsMenuItem}); this.BeatmapsContextMenuStrip.Name = "CollectionContextMenuStrip"; - this.BeatmapsContextMenuStrip.Size = new System.Drawing.Size(172, 114); + this.BeatmapsContextMenuStrip.Size = new System.Drawing.Size(182, 136); // // OpenDlMapMenuStrip // @@ -119,7 +121,7 @@ private void InitializeComponent() this.OpenBeatmapDownloadMapMenuStrip, this.OpenBeatmapFolderMenuStrip}); this.OpenDlMapMenuStrip.Name = "OpenDlMapMenuStrip"; - this.OpenDlMapMenuStrip.Size = new System.Drawing.Size(171, 22); + this.OpenDlMapMenuStrip.Size = new System.Drawing.Size(181, 22); this.OpenDlMapMenuStrip.Text = "Open"; // // OpenBeatmapPageMapMenuStrip @@ -164,7 +166,7 @@ private void InitializeComponent() this.DeleteMapMenuStrip.Enabled = false; this.DeleteMapMenuStrip.Name = "DeleteMapMenuStrip"; this.DeleteMapMenuStrip.ShortcutKeys = System.Windows.Forms.Keys.Delete; - this.DeleteMapMenuStrip.Size = new System.Drawing.Size(171, 22); + this.DeleteMapMenuStrip.Size = new System.Drawing.Size(181, 22); this.DeleteMapMenuStrip.Text = "Delete"; this.DeleteMapMenuStrip.Click += new System.EventHandler(this.MenuStripClick); // @@ -176,7 +178,7 @@ private void InitializeComponent() this.SearchTitleMapMenuStrip}); this.searchToolStripMenuItem.Enabled = false; this.searchToolStripMenuItem.Name = "searchToolStripMenuItem"; - this.searchToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + this.searchToolStripMenuItem.Size = new System.Drawing.Size(181, 22); this.searchToolStripMenuItem.Text = "Search"; // // SearchMapsetMapMenuStrip @@ -203,7 +205,7 @@ private void InitializeComponent() this.copyUrlMenuStrip, this.copyAsTextMenuStrip}); this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; - this.copyToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + this.copyToolStripMenuItem.Size = new System.Drawing.Size(181, 22); this.copyToolStripMenuItem.Text = "Copy"; // // copyUrlMenuStrip @@ -223,7 +225,7 @@ private void InitializeComponent() // PullMapsetMenuStrip // this.PullMapsetMenuStrip.Name = "PullMapsetMenuStrip"; - this.PullMapsetMenuStrip.Size = new System.Drawing.Size(171, 22); + this.PullMapsetMenuStrip.Size = new System.Drawing.Size(181, 22); this.PullMapsetMenuStrip.Text = "Pull whole mapset"; this.PullMapsetMenuStrip.Click += new System.EventHandler(this.MenuStripClick); // @@ -431,6 +433,13 @@ private void InitializeComponent() this.MainBpm.IsVisible = false; this.MainBpm.Text = "MainBpm"; // + // exportBeatmapSetsMenuItem + // + this.exportBeatmapSetsMenuItem.Name = "exportBeatmapSetsMenuItem"; + this.exportBeatmapSetsMenuItem.Size = new System.Drawing.Size(181, 22); + this.exportBeatmapSetsMenuItem.Text = "Export beatmap sets"; + this.exportBeatmapSetsMenuItem.Click += new System.EventHandler(this.MenuStripClick); + // // BeatmapListingView // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -490,5 +499,6 @@ private void InitializeComponent() private BrightIdeasSoftware.OLVColumn olvColumn9; private BrightIdeasSoftware.OLVColumn olvColumn11; private BrightIdeasSoftware.OLVColumn MainBpm; + private System.Windows.Forms.ToolStripMenuItem exportBeatmapSetsMenuItem; } } diff --git a/GuiComponents/Controls/BeatmapListingView.cs b/GuiComponents/Controls/BeatmapListingView.cs index 8290a01..e78dc64 100644 --- a/GuiComponents/Controls/BeatmapListingView.cs +++ b/GuiComponents/Controls/BeatmapListingView.cs @@ -289,6 +289,8 @@ private void MenuStripClick(object sender, EventArgs e) BeatmapOperation?.Invoke(this, Common.BeatmapListingAction.OpenBeatmapFolder); else if (sender == PullMapsetMenuStrip) BeatmapOperation?.Invoke(this, Common.BeatmapListingAction.PullWholeMapSet); + else if (sender == exportBeatmapSetsMenuItem) + BeatmapOperation?.Invoke(this, Common.BeatmapListingAction.ExportBeatmapSets); } private void ListViewBeatmaps_KeyUp(object sender, KeyEventArgs e) diff --git a/GuiComponents/ProgressForm.Designer.cs b/GuiComponents/ProgressForm.Designer.cs new file mode 100644 index 0000000..0de6760 --- /dev/null +++ b/GuiComponents/ProgressForm.Designer.cs @@ -0,0 +1,87 @@ +namespace GuiComponents +{ + partial class ProgressForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label_text = new System.Windows.Forms.Label(); + this.button_cancel = new System.Windows.Forms.Button(); + this.progressBar1 = new System.Windows.Forms.ProgressBar(); + this.SuspendLayout(); + // + // label_text + // + this.label_text.Location = new System.Drawing.Point(12, 9); + this.label_text.MaximumSize = new System.Drawing.Size(415, 91); + this.label_text.Name = "label_text"; + this.label_text.Size = new System.Drawing.Size(415, 81); + this.label_text.TabIndex = 4; + this.label_text.Text = "Text\r\n\r\nText\r\n\r\nText\r\n\r\nText\r\n"; + this.label_text.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // button_cancel + // + this.button_cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.button_cancel.DialogResult = System.Windows.Forms.DialogResult.No; + this.button_cancel.Location = new System.Drawing.Point(314, 138); + this.button_cancel.Name = "button_cancel"; + this.button_cancel.Size = new System.Drawing.Size(117, 27); + this.button_cancel.TabIndex = 5; + this.button_cancel.Text = "Cancel"; + this.button_cancel.UseVisualStyleBackColor = true; + // + // progressBar1 + // + this.progressBar1.Location = new System.Drawing.Point(13, 108); + this.progressBar1.Name = "progressBar1"; + this.progressBar1.Size = new System.Drawing.Size(418, 23); + this.progressBar1.TabIndex = 6; + // + // ProgressForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(443, 177); + this.Controls.Add(this.progressBar1); + this.Controls.Add(this.button_cancel); + this.Controls.Add(this.label_text); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ProgressForm"; + this.Text = "ProgressForm"; + this.ResumeLayout(false); + + } + + #endregion + + public System.Windows.Forms.Label label_text; + private System.Windows.Forms.Button button_cancel; + private System.Windows.Forms.ProgressBar progressBar1; + } +} \ No newline at end of file diff --git a/GuiComponents/ProgressForm.cs b/GuiComponents/ProgressForm.cs new file mode 100644 index 0000000..5225acb --- /dev/null +++ b/GuiComponents/ProgressForm.cs @@ -0,0 +1,44 @@ +using GuiComponents.Forms; +using GuiComponents.Interfaces; +using System; +using System.Windows.Forms; + +namespace GuiComponents +{ + public partial class ProgressForm : BaseForm, IProgressForm + { + public event EventHandler AbortClicked; + public ProgressForm() + { + InitializeComponent(); + } + + private const int CP_NOCLOSE_BUTTON = 0x200; + protected override CreateParams CreateParams + { + get + { + CreateParams myCp = base.CreateParams; + myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON; + return myCp; + } + } + internal static IProgressForm ShowDialog(Progress userProgressMessage, Progress completionPercentage) + { + var form = new ProgressForm(); + userProgressMessage.ProgressChanged += (_, message) => form.label_text.Text = message; + completionPercentage.ProgressChanged += form.CompletionPercentage_ProgressChanged; + form.button_cancel.Click += (_, __) => form.AbortClicked?.Invoke(form, EventArgs.Empty); + + form.Text = $"Collection Manager - Beatmap Export"; + return form; + } + + private void CompletionPercentage_ProgressChanged(object sender, int percentage) + { + progressBar1.Value = percentage; + if (percentage == progressBar1.Maximum) + button_cancel.Text = "Close"; + } + } +} diff --git a/GuiComponents/ProgressForm.resx b/GuiComponents/ProgressForm.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/GuiComponents/ProgressForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GuiComponents/UserDialogs.cs b/GuiComponents/UserDialogs.cs index fa70270..6056c24 100644 --- a/GuiComponents/UserDialogs.cs +++ b/GuiComponents/UserDialogs.cs @@ -15,6 +15,7 @@ public bool IsThisPathCorrect(string path) "Detected osu in: " + Environment.NewLine + path + Environment.NewLine + "Is this correct?", "osu! directory", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk); return dialogResult == DialogResult.Yes; + } public string SelectDirectory(string text) @@ -86,6 +87,11 @@ public bool YesNoMessageBox(string text, string caption, MessageBoxType messageB return (result.dialogResult == DialogResult.Yes, result.doNotAskAgain); } + public IProgressForm ProgressForm(Progress userProgressMessage, Progress completionPercentage) + { + return GuiComponents.ProgressForm.ShowDialog(userProgressMessage, completionPercentage); + } + public void OkMessageBox(string text, string caption, MessageBoxType messageBoxType = MessageBoxType.Info) { var icon = GetMessageBoxIcon(messageBoxType);