diff --git a/src/KKManager.Core/Functions/ZipmodTools.cs b/src/KKManager.Core/Functions/ZipmodTools.cs index 6fa30e3..2b669fe 100644 --- a/src/KKManager.Core/Functions/ZipmodTools.cs +++ b/src/KKManager.Core/Functions/ZipmodTools.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using KKManager.Data.Zipmods; +using KKManager.Util; namespace KKManager.Windows { @@ -121,7 +122,7 @@ private static void SafeFileDelete(string file) try { if (!_simulate) - File.Delete(file); + file.SafeDelete().Wait(); } catch (SystemException) { diff --git a/src/KKManager.Core/KKManager.Core.csproj b/src/KKManager.Core/KKManager.Core.csproj index 5ee28f2..16dd65b 100644 --- a/src/KKManager.Core/KKManager.Core.csproj +++ b/src/KKManager.Core/KKManager.Core.csproj @@ -70,6 +70,7 @@ ..\packages\Microsoft.NET.StringTools.17.7.2\lib\net472\Microsoft.NET.StringTools.dll + ..\packages\Mono.Cecil.0.11.5\lib\net40\Mono.Cecil.dll diff --git a/src/KKManager.Core/Properties/Settings.Designer.cs b/src/KKManager.Core/Properties/Settings.Designer.cs index cff521e..f5963f4 100644 --- a/src/KKManager.Core/Properties/Settings.Designer.cs +++ b/src/KKManager.Core/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace KKManager.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -199,5 +199,17 @@ public bool P2P_SettingsShown { this["P2P_SettingsShown"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool DeleteToRecycleBin { + get { + return ((bool)(this["DeleteToRecycleBin"])); + } + set { + this["DeleteToRecycleBin"] = value; + } + } } } diff --git a/src/KKManager.Core/Properties/Settings.settings b/src/KKManager.Core/Properties/Settings.settings index b55d0ac..3bec94e 100644 --- a/src/KKManager.Core/Properties/Settings.settings +++ b/src/KKManager.Core/Properties/Settings.settings @@ -44,5 +44,8 @@ False + + True + \ No newline at end of file diff --git a/src/KKManager.Core/Util/Extensions.cs b/src/KKManager.Core/Util/Extensions.cs index deaa388..3ca7345 100644 --- a/src/KKManager.Core/Util/Extensions.cs +++ b/src/KKManager.Core/Util/Extensions.cs @@ -2,12 +2,15 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Xml.Linq; using BrightIdeasSoftware; +using KKManager.Properties; +using Microsoft.VisualBasic.FileIO; using SharpCompress.Archives; using SharpCompress.Readers; @@ -113,7 +116,7 @@ public static string GetFullNameWithDifferentExtension(this FileInfo fi, string { if (fi == null) throw new ArgumentNullException(nameof(fi)); if (newExtension == null) throw new ArgumentNullException(nameof(newExtension)); - + return Path.Combine(fi.DirectoryName ?? throw new InvalidOperationException("DirectoryName null for " + fi), fi.GetNameWithoutExtension() + newExtension); } @@ -141,5 +144,75 @@ public static XElement GetOrAddElement(this XElement e, string name) public static void AddObjects(this ObjectListView olv, IList modelObjects) => olv.AddObjects((ICollection)modelObjects); public static void RefreshObjects(this ObjectListView olv, IList modelObjects) => olv.RefreshObjects((IList)modelObjects); + + public static async Task SafeDelete(this FileSystemInfo info) + { + if (info == null) return; + + var toRecycleBin = Settings.Default.DeleteToRecycleBin; + + try + { + if (info.Exists) + { + // Prevent issues removing readonly files + info.Attributes = FileAttributes.Normal; + + if (info is DirectoryInfo dir) + { + try + { + if (toRecycleBin) + FileSystem.DeleteDirectory(dir.FullName, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin); + else + dir.Delete(true); + } + catch + { + await Task.Delay(100); + dir.Delete(true); + } + } + else if (info is FileInfo file) + { + try + { + if (toRecycleBin) + FileSystem.DeleteFile(file.FullName, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin); + else + file.Delete(); + } + catch + { + await Task.Delay(100); + file.Delete(); + } + } + else + { + throw new InvalidOperationException("wtf is" + info.GetType().AssemblyQualifiedName); + } + } + } + catch (Exception ex) + { + if (ex is AggregateException ae && ae.InnerExceptions.Count == 1) + ex = ae.InnerExceptions[0]; + + Console.WriteLine($"Failed to delete [{info.FullName}] because of error: {ex.ToStringDemystified()}"); + + throw; + } + } + + public static Task SafeDelete(this string filename) + { + if (File.Exists(filename)) + return new FileInfo(filename).SafeDelete(); + else if (Directory.Exists(filename)) + return new DirectoryInfo(filename).SafeDelete(); + else + return Task.CompletedTask; + } } } diff --git a/src/KKManager.Core/app.config b/src/KKManager.Core/app.config index 0bb9c3d..e8c17bc 100644 --- a/src/KKManager.Core/app.config +++ b/src/KKManager.Core/app.config @@ -97,6 +97,9 @@ False + + True + diff --git a/src/KKManager.Updater/Data/UpdateItem.cs b/src/KKManager.Updater/Data/UpdateItem.cs index 2e6ceee..9f0be4f 100644 --- a/src/KKManager.Updater/Data/UpdateItem.cs +++ b/src/KKManager.Updater/Data/UpdateItem.cs @@ -111,9 +111,7 @@ public async Task Update(Progress progressCallback, CancellationToken ca if (TargetPath.Exists) { Console.WriteLine($"Deleting old file {TargetPath.FullName}"); - // Prevent issues removing readonly files - TargetPath.Attributes = FileAttributes.Normal; - TargetPath.Delete(); + await TargetPath.SafeDelete(); // Make sure the file gets deleted before continuing await Task.Delay(200, cancellationToken).ConfigureAwait(false); } diff --git a/src/KKManager.Updater/Sources/FtpUpdater.cs b/src/KKManager.Updater/Sources/FtpUpdater.cs index a4168e5..2a88839 100644 --- a/src/KKManager.Updater/Sources/FtpUpdater.cs +++ b/src/KKManager.Updater/Sources/FtpUpdater.cs @@ -208,7 +208,7 @@ private IEnumerable GetSubNodes(FtpListItem remoteDir) private async Task UpdateItem(FtpListItem sourceItem, FileInfo targetPath, IProgress progressCallback, CancellationToken cancellationToken) { // Delete old file if any exists so the download doesn't try to append to it. Append mode is needed for retrying downloads to resume instead of restarting - targetPath.Delete(); + await targetPath.SafeDelete(); await Connect(cancellationToken).ConfigureAwait(false); diff --git a/src/KKManager/Windows/Content/CardWindow.cs b/src/KKManager/Windows/Content/CardWindow.cs index 7c5f786..25863c8 100644 --- a/src/KKManager/Windows/Content/CardWindow.cs +++ b/src/KKManager/Windows/Content/CardWindow.cs @@ -560,7 +560,7 @@ private void renameCardsToolStripMenuItem_Click(object sender, EventArgs e) RenameCards.ShowDialog(this, _typedListView.SelectedObjects.ToArray()); } - private void toolStripButtonDelete_Click(object sender, EventArgs e) + private async void toolStripButtonDelete_Click(object sender, EventArgs e) { var selectedObjects = _typedListView.SelectedObjects; if (!selectedObjects.Any()) return; @@ -571,7 +571,7 @@ private void toolStripButtonDelete_Click(object sender, EventArgs e) { try { - selectedObject.Location.Delete(); + await selectedObject.Location.SafeDelete(); } catch (Exception exception) { diff --git a/src/KKManager/Windows/Content/PluginsWindow.cs b/src/KKManager/Windows/Content/PluginsWindow.cs index 9351048..a475512 100644 --- a/src/KKManager/Windows/Content/PluginsWindow.cs +++ b/src/KKManager/Windows/Content/PluginsWindow.cs @@ -97,7 +97,7 @@ private void SideloaderModsWindow_FormClosed(object sender, FormClosedEventArgs CancelRefreshing(); } - private void toolStripButtonDelete_Click(object sender, EventArgs e) + private async void toolStripButtonDelete_Click(object sender, EventArgs e) { if (MessageBox.Show("This will permanently delete all selected plugins, are you sure you want to continue?", "Delete plugins", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != DialogResult.OK) @@ -116,7 +116,7 @@ private void toolStripButtonDelete_Click(object sender, EventArgs e) { try { - plug.Location.Delete(); + await plug.Location.SafeDelete(); objectListView1.RemoveObject(plug); } catch (SystemException ex) diff --git a/src/KKManager/Windows/Content/SideloaderModsWindow.cs b/src/KKManager/Windows/Content/SideloaderModsWindow.cs index 57341c1..a2f8455 100644 --- a/src/KKManager/Windows/Content/SideloaderModsWindow.cs +++ b/src/KKManager/Windows/Content/SideloaderModsWindow.cs @@ -129,7 +129,7 @@ private void toolStripButtonDisable_Click(object sender, EventArgs e) SetZipmodEnabled(false, _listView.SelectedObjects); } - private void toolStripButtonDelete_Click(object sender, EventArgs e) + private async void toolStripButtonDelete_Click(object sender, EventArgs e) { if (MessageBox.Show("This will permanently delete all selected zipmods, are you sure you want to continue?", "Delete zipmods", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) != DialogResult.OK) @@ -139,7 +139,7 @@ private void toolStripButtonDelete_Click(object sender, EventArgs e) { try { - obj.Location.Delete(); + await obj.Location.SafeDelete(); objectListView1.RemoveObject(obj); } catch (SystemException ex) diff --git a/src/KKManager/Windows/MainWindow.Designer.cs b/src/KKManager/Windows/MainWindow.Designer.cs index 4365ef3..dc8b842 100644 --- a/src/KKManager/Windows/MainWindow.Designer.cs +++ b/src/KKManager/Windows/MainWindow.Designer.cs @@ -33,6 +33,8 @@ private void InitializeComponent() System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow)); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.openGameLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator(); this.changeGameInstallDirectoryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator(); this.exitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -85,8 +87,7 @@ private void InitializeComponent() this.dockPanel = new WeifenLuo.WinFormsUI.Docking.DockPanel(); this.statusStrip1 = new System.Windows.Forms.StatusStrip(); this.toolStripStatusLabelStatus = new System.Windows.Forms.ToolStripStatusLabel(); - this.openGameLogToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator7 = new System.Windows.Forms.ToolStripSeparator(); + this.tryToDeleteToRecycleBinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.SuspendLayout(); @@ -118,6 +119,17 @@ private void InitializeComponent() this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; resources.ApplyResources(this.fileToolStripMenuItem, "fileToolStripMenuItem"); // + // openGameLogToolStripMenuItem + // + this.openGameLogToolStripMenuItem.Name = "openGameLogToolStripMenuItem"; + resources.ApplyResources(this.openGameLogToolStripMenuItem, "openGameLogToolStripMenuItem"); + this.openGameLogToolStripMenuItem.Click += new System.EventHandler(this.openGameLogToolStripMenuItem_Click); + // + // toolStripSeparator7 + // + this.toolStripSeparator7.Name = "toolStripSeparator7"; + resources.ApplyResources(this.toolStripSeparator7, "toolStripSeparator7"); + // // changeGameInstallDirectoryToolStripMenuItem // this.changeGameInstallDirectoryToolStripMenuItem.Name = "changeGameInstallDirectoryToolStripMenuItem"; @@ -377,7 +389,8 @@ private void InitializeComponent() // settingsToolStripMenuItem // this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.languagesToolStripMenuItem}); + this.languagesToolStripMenuItem, + this.tryToDeleteToRecycleBinToolStripMenuItem}); this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem"; resources.ApplyResources(this.settingsToolStripMenuItem, "settingsToolStripMenuItem"); this.settingsToolStripMenuItem.DropDownOpening += new System.EventHandler(this.settingsToolStripMenuItem_DropDownOpening); @@ -457,16 +470,10 @@ private void InitializeComponent() this.toolStripStatusLabelStatus.Name = "toolStripStatusLabelStatus"; resources.ApplyResources(this.toolStripStatusLabelStatus, "toolStripStatusLabelStatus"); // - // openGameLogToolStripMenuItem - // - this.openGameLogToolStripMenuItem.Name = "openGameLogToolStripMenuItem"; - resources.ApplyResources(this.openGameLogToolStripMenuItem, "openGameLogToolStripMenuItem"); - this.openGameLogToolStripMenuItem.Click += new System.EventHandler(this.openGameLogToolStripMenuItem_Click); - // - // toolStripSeparator7 + // tryToDeleteToRecycleBinToolStripMenuItem // - this.toolStripSeparator7.Name = "toolStripSeparator7"; - resources.ApplyResources(this.toolStripSeparator7, "toolStripSeparator7"); + this.tryToDeleteToRecycleBinToolStripMenuItem.Name = "tryToDeleteToRecycleBinToolStripMenuItem"; + resources.ApplyResources(this.tryToDeleteToRecycleBinToolStripMenuItem, "tryToDeleteToRecycleBinToolStripMenuItem"); // // MainWindow // @@ -547,5 +554,6 @@ private void InitializeComponent() private ToolStripMenuItem openIndividualDownloadWebsiteToolStripMenuItem; private ToolStripMenuItem openGameLogToolStripMenuItem; private ToolStripSeparator toolStripSeparator7; + private ToolStripMenuItem tryToDeleteToRecycleBinToolStripMenuItem; } } \ No newline at end of file diff --git a/src/KKManager/Windows/MainWindow.cs b/src/KKManager/Windows/MainWindow.cs index d6d304e..dda58de 100644 --- a/src/KKManager/Windows/MainWindow.cs +++ b/src/KKManager/Windows/MainWindow.cs @@ -65,6 +65,7 @@ public MainWindow() Settings.Default.Binder.BindControl(checkForUpdatesOnStartupToolStripMenuItem, settings => settings.AutoUpdateSearch, this); Settings.Default.Binder.BindControl(useSystemProxyServerToolStripMenuItem, settings => settings.UseProxy, this); + Settings.Default.Binder.BindControl(tryToDeleteToRecycleBinToolStripMenuItem, settings => settings.DeleteToRecycleBin, this); Settings.Default.Binder.SendUpdates(this); // Before using the window location, check if isn't the default value and that it's actually visible on the screen diff --git a/src/KKManager/Windows/MainWindow.resx b/src/KKManager/Windows/MainWindow.resx index ece5de8..304e06c 100644 --- a/src/KKManager/Windows/MainWindow.resx +++ b/src/KKManager/Windows/MainWindow.resx @@ -356,11 +356,22 @@ &Tools - 126, 22 + 215, 22 Language + + 215, 22 + + + Try to delete to Recycle Bin + + + Attempt to move files to the recycle bin when deleting cards/mods/plugins and when updating mods. +If this fails for whatever reason (e.g. recycle bin is full or disabled) the files are deleted permanently. + + 61, 20 @@ -2164,6 +2175,18 @@ System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + openGameLogToolStripMenuItem + + + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + toolStripSeparator7 + + + System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + changeGameInstallDirectoryToolStripMenuItem @@ -2464,18 +2487,12 @@ System.Windows.Forms.ToolStripStatusLabel, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - openGameLogToolStripMenuItem + + tryToDeleteToRecycleBinToolStripMenuItem - + System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - toolStripSeparator7 - - - System.Windows.Forms.ToolStripSeparator, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - MainWindow