diff --git a/XrmToolBox.PluginsStore/StoreForm - Copier.cs b/XrmToolBox.PluginsStore/StoreForm - Copier.cs index 3d37db0c..37323beb 100644 --- a/XrmToolBox.PluginsStore/StoreForm - Copier.cs +++ b/XrmToolBox.PluginsStore/StoreForm - Copier.cs @@ -374,11 +374,31 @@ public PluginUpdates PrepareInstallationPackages(List xtbPackag } continue; } - manager.InstallPackage(xtbPackage.Package, true, false); var packageFolder = Path.Combine(nugetPluginsFolder, xtbPackage.Package.Id + "." + xtbPackage.Package.Version); + try + { + manager.InstallPackage(xtbPackage.Package, true, false); + } + catch + { + // Clean up partially extracted package folder on failure + if (Directory.Exists(packageFolder)) + { + try + { + Directory.Delete(packageFolder, true); + } + catch + { + // Best effort cleanup - ignore errors during cleanup + } + } + throw; + } + foreach (var fi in xtbPackage.Package.GetFiles()) { var destinationFile = Path.Combine(applicationDataFolder, fi.EffectivePath); @@ -425,6 +445,8 @@ public void PerformInstallation(PluginUpdates updates) } else { + var copiedFiles = new List(); + foreach (var pu in updates.Plugins) { try @@ -436,9 +458,26 @@ public void PerformInstallation(PluginUpdates updates) Directory.CreateDirectory(destinationDirectory); } File.Copy(pu.Source, pu.Destination, true); + copiedFiles.Add(pu.Destination); } catch (Exception error) { + // Clean up files that were copied before the failure + foreach (var copiedFile in copiedFiles) + { + try + { + if (File.Exists(copiedFile)) + { + File.Delete(copiedFile); + } + } + catch + { + // Best effort cleanup - ignore errors during cleanup + } + } + MessageBox.Show(this, "An error occured while copying files: " + error.Message + "\r\n\r\nCopy has been aborted", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); diff --git a/XrmToolBox.PluginsStore/StoreFromPortal.cs b/XrmToolBox.PluginsStore/StoreFromPortal.cs index b66c33b4..dcec98e6 100644 --- a/XrmToolBox.PluginsStore/StoreFromPortal.cs +++ b/XrmToolBox.PluginsStore/StoreFromPortal.cs @@ -324,6 +324,8 @@ public bool PerformInstallation(PluginUpdates updates, Form form) return false; } + var copiedFiles = new List(); + foreach (var pu in updates.Plugins) { try @@ -340,9 +342,26 @@ public bool PerformInstallation(PluginUpdates updates, Form form) Directory.CreateDirectory(destinationDirectory); } File.Copy(pu.Source, pu.Destination, true); + copiedFiles.Add(pu.Destination); } catch (Exception error) { + // Clean up files that were copied before the failure + foreach (var copiedFile in copiedFiles) + { + try + { + if (File.Exists(copiedFile)) + { + File.Delete(copiedFile); + } + } + catch + { + // Best effort cleanup - ignore errors during cleanup + } + } + MessageBox.Show("An error occured while copying files: " + error.Message + "\r\n\r\nCopy has been aborted", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; @@ -399,58 +418,77 @@ public async Task PrepareInstallationPackages(List plu var packageFolder = Path.Combine(nugetPluginsFolder, $"{plugin.NugetId}.{version.Version}"); - using (PackageArchiveReader packageReader = new PackageArchiveReader(packageStream)) + try { - foreach (var packageFile in await packageReader.GetFilesAsync(cancellationToken)) + using (PackageArchiveReader packageReader = new PackageArchiveReader(packageStream)) { - if (packageFile.ToLower().IndexOf("plugins/") >= 0) + foreach (var packageFile in await packageReader.GetFilesAsync(cancellationToken)) { - if (!Directory.Exists(packageFolder)) + if (packageFile.ToLower().IndexOf("plugins/") >= 0) { - Directory.CreateDirectory(packageFolder); - } + if (!Directory.Exists(packageFolder)) + { + Directory.CreateDirectory(packageFolder); + } - var relativeFilePath = packageFile.Remove(0, packageFile.ToLower().IndexOf("plugins/") + 8); - var filePath = Path.Combine(packageFolder, relativeFilePath); - var fi = new FileInfo(filePath); + var relativeFilePath = packageFile.Remove(0, packageFile.ToLower().IndexOf("plugins/") + 8); + var filePath = Path.Combine(packageFolder, relativeFilePath); + var fi = new FileInfo(filePath); - if (!Directory.Exists(fi.Directory.FullName)) - { - Directory.CreateDirectory(fi.Directory.FullName); - } + if (!Directory.Exists(fi.Directory.FullName)) + { + Directory.CreateDirectory(fi.Directory.FullName); + } - using (var fileStream = File.OpenWrite(filePath)) - using (var stream = await packageReader.GetStreamAsync(packageFile, cancellationToken)) - { - await stream.CopyToAsync(fileStream); - } + using (var fileStream = File.OpenWrite(filePath)) + using (var stream = await packageReader.GetStreamAsync(packageFile, cancellationToken)) + { + await stream.CopyToAsync(fileStream); + } - var destinationFile = Path.Combine(Paths.PluginsPath, relativeFilePath); + var destinationFile = Path.Combine(Paths.PluginsPath, relativeFilePath); - // XrmToolBox restart is required when a plugin has to be - // updated or when a new plugin shares files with other - // plugin(s) already installed - if (plugin.RequiresXtbRestart) - { - pus.Plugins.Add(new PluginUpdate + // XrmToolBox restart is required when a plugin has to be + // updated or when a new plugin shares files with other + // plugin(s) already installed + if (plugin.RequiresXtbRestart) { - Source = filePath, - Destination = destinationFile, - RequireRestart = true - }); - } - else if (plugin.Action == PackageInstallAction.Install) - { - pus.Plugins.Add(new PluginUpdate + pus.Plugins.Add(new PluginUpdate + { + Source = filePath, + Destination = destinationFile, + RequireRestart = true + }); + } + else if (plugin.Action == PackageInstallAction.Install) { - Source = filePath, - Destination = destinationFile, - RequireRestart = false - }); + pus.Plugins.Add(new PluginUpdate + { + Source = filePath, + Destination = destinationFile, + RequireRestart = false + }); + } } } } } + catch + { + // Clean up partially extracted package folder on failure + if (Directory.Exists(packageFolder)) + { + try + { + Directory.Delete(packageFolder, true); + } + catch + { + // Best effort cleanup - ignore errors during cleanup + } + } + throw; + } } } diff --git a/XrmToolBox.ToolLibrary/ToolLibrary.cs b/XrmToolBox.ToolLibrary/ToolLibrary.cs index 413a6e68..65768469 100644 --- a/XrmToolBox.ToolLibrary/ToolLibrary.cs +++ b/XrmToolBox.ToolLibrary/ToolLibrary.cs @@ -226,69 +226,88 @@ public void DownloadPackage(ToolOperationEventArgs e, PluginUpdates pus) { Directory.CreateDirectory(cachePackagePath); - Uri pathUri = new Uri(e.Plugin.DownloadUrl); - byte[] packageBytes; - if (pathUri.Scheme == "http" || pathUri.Scheme == "https") - { - packageBytes = HttpClient.GetByteArrayAsync(e.DownloadUrl).GetAwaiter().GetResult(); - } - else if (pathUri.Scheme == "file") - { - packageBytes = File.ReadAllBytes(e.DownloadUrl); - } - else + try { - throw new Exception($"Unsupported file path scheme {pathUri.Scheme}"); - } + Uri pathUri = new Uri(e.Plugin.DownloadUrl); + byte[] packageBytes; + if (pathUri.Scheme == "http" || pathUri.Scheme == "https") + { + packageBytes = HttpClient.GetByteArrayAsync(e.DownloadUrl).GetAwaiter().GetResult(); + } + else if (pathUri.Scheme == "file") + { + packageBytes = File.ReadAllBytes(e.DownloadUrl); + } + else + { + throw new Exception($"Unsupported file path scheme {pathUri.Scheme}"); + } - using (var ms = new MemoryStream()) - { - ms.Write(packageBytes, 0, packageBytes.Length); - var package = Package.Open(ms); + using (var ms = new MemoryStream()) + { + ms.Write(packageBytes, 0, packageBytes.Length); + var package = Package.Open(ms); - bool found = false; + bool found = false; - foreach (var part in package.GetParts()) - { - if (part.Uri.ToString().ToLower().IndexOf("/plugins/") < 0) continue; + foreach (var part in package.GetParts()) + { + if (part.Uri.ToString().ToLower().IndexOf("/plugins/") < 0) continue; - found = true; + found = true; - var fileName = part.Uri.ToString().Split(new string[] { "/Plugins/", "/plugins/" }, StringSplitOptions.RemoveEmptyEntries).Last(); - fullPath = Path.Combine(cachePackagePath, fileName); - destinationFile = Path.Combine(Paths.PluginsPath, fileName); + var fileName = part.Uri.ToString().Split(new string[] { "/Plugins/", "/plugins/" }, StringSplitOptions.RemoveEmptyEntries).Last(); + fullPath = Path.Combine(cachePackagePath, fileName); + destinationFile = Path.Combine(Paths.PluginsPath, fileName); - var directory = Path.GetDirectoryName(fullPath); - if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); + var directory = Path.GetDirectoryName(fullPath); + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); - using (var partStream = part.GetStream()) - { - using (var fileStream = File.Create(fullPath)) + using (var partStream = part.GetStream()) { - partStream.Seek(0, SeekOrigin.Begin); - partStream.CopyTo(fileStream); + using (var fileStream = File.Create(fullPath)) + { + partStream.Seek(0, SeekOrigin.Begin); + partStream.CopyTo(fileStream); + } + } + + // XrmToolBox restart is required when a plugin has to be + // updated or when a new plugin shares files with other + // plugin(s) already installed + if (e.Plugin.RequiresXtbRestart || e.Plugin.Action == PackageInstallAction.Install) + { + pus.Plugins.Add(new PluginUpdate + { + Name = e.Plugin.Name, + Source = fullPath, + Destination = destinationFile, + RequireRestart = e.Plugin.RequiresXtbRestart + }); } } - // XrmToolBox restart is required when a plugin has to be - // updated or when a new plugin shares files with other - // plugin(s) already installed - if (e.Plugin.RequiresXtbRestart || e.Plugin.Action == PackageInstallAction.Install) + if (!found) { - pus.Plugins.Add(new PluginUpdate - { - Name = e.Plugin.Name, - Source = fullPath, - Destination = destinationFile, - RequireRestart = e.Plugin.RequiresXtbRestart - }); + throw new Exception("No plugin files found in package"); } } - - if (!found) + } + catch + { + // Clean up partially extracted package folder on failure + if (Directory.Exists(cachePackagePath)) { - throw new Exception("No plugin files found in package"); + try + { + Directory.Delete(cachePackagePath, true); + } + catch + { + // Best effort cleanup - ignore errors during cleanup + } } + throw; } } else