From f56f26df64c01e3981b26e49883320d47fea1d2d Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 14 Jun 2016 11:57:09 -0700 Subject: [PATCH 1/4] Delete ExternalTools directory on Npm Cache Clear This change updates the npm cache clear button to also delete our ExternalTools directory. This directory contains node packages used internally by NTVS, so we want to provide a way to delete it if users get in a bad state. Also extracted the localappdata paths for NTVS to constants. closes #1044 --- Nodejs/Product/Nodejs/NodejsConstants.cs | 28 ++++++++++++++ .../Nodejs/Options/NodejsNpmOptionsControl.cs | 38 +++++++++---------- .../Nodejs/Options/NodejsNpmOptionsPage.cs | 13 ------- .../Product/Nodejs/Project/NodeModulesNode.cs | 2 +- Nodejs/Product/Nodejs/TypingsAcquisition.cs | 19 ++-------- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/Nodejs/Product/Nodejs/NodejsConstants.cs b/Nodejs/Product/Nodejs/NodejsConstants.cs index 8c53edd2d..a2770ecad 100644 --- a/Nodejs/Product/Nodejs/NodejsConstants.cs +++ b/Nodejs/Product/Nodejs/NodejsConstants.cs @@ -15,6 +15,7 @@ //*********************************************************// using System; +using System.IO; namespace Microsoft.NodejsTools { internal class NodejsConstants { @@ -73,6 +74,33 @@ internal class NodejsConstants { internal const string NodeToolsProcessIdEnvironmentVariable = "_NTVS_PID"; + private static string NtvsLocalAppData { + get { + return Path.Combine( + System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), + "Microsoft", + "Node.js Tools"); + } + } + + /// + /// Path to the private package where NTVS tools are installed. + /// + public static string ExternalToolsPath { + get { + return Path.Combine(NtvsLocalAppData, "ExternalTools"); + } + + } + /// + /// Path to where NTVS caches Npm data. + /// + public static string NpmCachePath { + get { + return Path.Combine(NtvsLocalAppData, "NpmCache"); + } + } + /// /// Checks whether a relative and double-backslashed seperated path contains a folder name. /// diff --git a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs index 5f78f45ce..fd96eaab2 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs @@ -19,21 +19,15 @@ using System.Windows.Forms; using Microsoft.NodejsTools.Project; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudioTools; namespace Microsoft.NodejsTools.Options { public partial class NodejsNpmOptionsControl : UserControl { - - private string _npmCachePath; - public NodejsNpmOptionsControl() { InitializeComponent(); } - internal void SyncControlWithPageSettings(NodejsNpmOptionsPage page) { _showOutputWhenRunningNpm.Checked = page.ShowOutputWindowWhenExecutingNpm; - _npmCachePath = page.NpmCachePath; _cacheClearedSuccessfully.Visible = false; } @@ -42,20 +36,26 @@ internal void SyncPageWithControlSettings(NodejsNpmOptionsPage page) { } private void ClearCacheButton_Click(object sender, EventArgs e) { + bool didClearNpmCache = DeleteCacheDirectory("npm cache", NodejsConstants.NpmCachePath); + bool didClearTools = DeleteCacheDirectory("NTVS external tools", NodejsConstants.ExternalToolsPath); + + _cacheClearedSuccessfully.Visible = (didClearNpmCache && didClearTools); + } + + private static bool DeleteCacheDirectory(string displayName, string cachePath) { try { - Directory.Delete(_npmCachePath, true); - _cacheClearedSuccessfully.Visible = true; + Directory.Delete(cachePath, true); + return true; } catch (DirectoryNotFoundException) { // Directory has already been deleted. Do nothing. - _cacheClearedSuccessfully.Visible = true; + return true; } catch (IOException exception) { // files are in use or path is too long MessageBox.Show( - string.Format("Cannot clear npm cache. {0}", exception.Message), - "Cannot Clear npm Cache", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); + string.Format("Cannot clear {0}. {1}", displayName, exception.Message), + string.Format("Cannot Clear {0}", displayName), + MessageBoxButtons.OK, + MessageBoxIcon.Information); } catch (Exception exception) { try { ActivityLog.LogError(SR.ProductName, exception.ToString()); @@ -64,12 +64,12 @@ private void ClearCacheButton_Click(object sender, EventArgs e) { } MessageBox.Show( - string.Format("Cannot clear npm cache. Try manually deleting the directory: {0}", _npmCachePath), - "Cannot Clear npm Cache", - MessageBoxButtons.OK, - MessageBoxIcon.Information - ); + string.Format("Cannot clear {0}. Try manually deleting the directory: {1}", displayName, cachePath), + string.Format("Cannot Clear {0}", displayName), + MessageBoxButtons.OK, + MessageBoxIcon.Information); } + return false; } } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsPage.cs index 6aa9eb4c9..fdd785606 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsPage.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsPage.cs @@ -14,8 +14,6 @@ // //*********************************************************// -using System; -using System.IO; using System.Runtime.InteropServices; using System.Windows.Forms; @@ -47,17 +45,6 @@ protected override IWin32Window Window { /// public bool ShowOutputWindowWhenExecutingNpm { get; set; } - public string NpmCachePath { - get { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Microsoft", - "Node.js Tools", - "NpmCache" - ); - } - } - /// /// Resets settings back to their defaults. This should be followed by /// a call to to commit the new diff --git a/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs b/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs index d30721835..127223a3c 100644 --- a/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs +++ b/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs @@ -138,7 +138,7 @@ public string PathToNpm { private static INpmController DefaultNpmController(string projectHome, NpmPathProvider pathProvider) { return NpmControllerFactory.Create( projectHome, - NodejsPackage.Instance.NpmOptionsPage.NpmCachePath, + NodejsConstants.NpmCachePath, false, pathProvider); } diff --git a/Nodejs/Product/Nodejs/TypingsAcquisition.cs b/Nodejs/Product/Nodejs/TypingsAcquisition.cs index 1c304cf26..e103797de 100644 --- a/Nodejs/Product/Nodejs/TypingsAcquisition.cs +++ b/Nodejs/Product/Nodejs/TypingsAcquisition.cs @@ -35,26 +35,13 @@ internal class TypingsAcquisition { private static SemaphoreSlim typingsToolGlobalWorkSemaphore = new SemaphoreSlim(1); - /// - /// Path the the private package where the typings acquisition tool is installed. - /// - private static string NtvsExternalToolsPath { - get { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "Microsoft", - "Node.js Tools", - "ExternalTools"); - } - } - /// /// Full path to the typings acquisition tool. /// private static string TypingsToolPath { get { return Path.Combine( - NtvsExternalToolsPath, + NodejsConstants.ExternalToolsPath, "node_modules", ".bin", TypingsToolExe); @@ -155,11 +142,11 @@ private async Task EnsureTypingsToolInstalled() { private async Task InstallTypingsTool() { _didTryToInstallTypingsTool = true; - Directory.CreateDirectory(NtvsExternalToolsPath); + Directory.CreateDirectory(NodejsConstants.ExternalToolsPath); // install typings using (var commander = _npmController.CreateNpmCommander()) { - return await commander.InstallPackageToFolderByVersionAsync(NtvsExternalToolsPath, TypingsTool, TypingsToolVersion, false); + return await commander.InstallPackageToFolderByVersionAsync(NodejsConstants.ExternalToolsPath, TypingsTool, TypingsToolVersion, false); } } From 8226db96e2da4d251b011032ab862209149997d4 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 22 Jun 2016 13:33:46 -0700 Subject: [PATCH 2/4] Addressing comments --- Nodejs/Product/Nodejs/NodejsConstants.cs | 2 +- .../Nodejs/Options/NodejsNpmOptionsControl.cs | 32 +++++++++---------- .../Nodejs/Project/ProjectResources.cs | 2 ++ Nodejs/Product/Nodejs/Resources.resx | 6 ++++ 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/Nodejs/Product/Nodejs/NodejsConstants.cs b/Nodejs/Product/Nodejs/NodejsConstants.cs index a2770ecad..ff83ab9bb 100644 --- a/Nodejs/Product/Nodejs/NodejsConstants.cs +++ b/Nodejs/Product/Nodejs/NodejsConstants.cs @@ -74,7 +74,7 @@ internal class NodejsConstants { internal const string NodeToolsProcessIdEnvironmentVariable = "_NTVS_PID"; - private static string NtvsLocalAppData { + public static string NtvsLocalAppData { get { return Path.Combine( System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData), diff --git a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs index fd96eaab2..58e9c6778 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs @@ -36,38 +36,36 @@ internal void SyncPageWithControlSettings(NodejsNpmOptionsPage page) { } private void ClearCacheButton_Click(object sender, EventArgs e) { - bool didClearNpmCache = DeleteCacheDirectory("npm cache", NodejsConstants.NpmCachePath); - bool didClearTools = DeleteCacheDirectory("NTVS external tools", NodejsConstants.ExternalToolsPath); + bool didClearNpmCache = TryDeleteCacheDirectory(NodejsConstants.NpmCachePath); + bool didClearTools = TryDeleteCacheDirectory(NodejsConstants.ExternalToolsPath); - _cacheClearedSuccessfully.Visible = (didClearNpmCache && didClearTools); + if (!didClearNpmCache || !didClearTools) { + MessageBox.Show( + SR.GetString(SR.CacheDirectoryClearFailedCaption, NodejsConstants.NtvsLocalAppData), + SR.GetString(SR.CacheDirectoryClearFailedTitle), + MessageBoxButtons.OK, + MessageBoxIcon.Information); + } + + _cacheClearedSuccessfully.Visible = didClearNpmCache && didClearTools; } - private static bool DeleteCacheDirectory(string displayName, string cachePath) { + private static bool TryDeleteCacheDirectory(string cachePath) { try { Directory.Delete(cachePath, true); - return true; + return true; } catch (DirectoryNotFoundException) { // Directory has already been deleted. Do nothing. return true; - } catch (IOException exception) { + } catch (IOException) { // files are in use or path is too long - MessageBox.Show( - string.Format("Cannot clear {0}. {1}", displayName, exception.Message), - string.Format("Cannot Clear {0}", displayName), - MessageBoxButtons.OK, - MessageBoxIcon.Information); + return false; } catch (Exception exception) { try { ActivityLog.LogError(SR.ProductName, exception.ToString()); } catch (InvalidOperationException) { // Activity Log is unavailable. } - - MessageBox.Show( - string.Format("Cannot clear {0}. Try manually deleting the directory: {1}", displayName, cachePath), - string.Format("Cannot Clear {0}", displayName), - MessageBoxButtons.OK, - MessageBoxIcon.Information); } return false; } diff --git a/Nodejs/Product/Nodejs/Project/ProjectResources.cs b/Nodejs/Product/Nodejs/Project/ProjectResources.cs index b80065269..bb52df5ff 100644 --- a/Nodejs/Product/Nodejs/Project/ProjectResources.cs +++ b/Nodejs/Product/Nodejs/Project/ProjectResources.cs @@ -31,6 +31,8 @@ internal class SR : CommonSR { internal const string CatalogLoadingNoNpm = "CatalogLoadingNoNpm"; internal const string CategoryStatus = "CategoryStatus"; internal const string CategoryVersion = "CategoryVersion"; + internal const string CacheDirectoryClearFailedTitle = "CacheDirectoryClearFailedTitle"; + internal const string CacheDirectoryClearFailedCaption = "CacheDirectoryClearFailedCaption"; internal const string ContinueWithoutAzureToolsUpgrade = "ContinueWithoutAzureToolsUpgrade"; internal const string DebuggerConnectionClosed = "DebuggerConnectionClosed"; internal const string DebuggerModuleUpdateFailed = "DebuggerModuleUpdateFailed"; diff --git a/Nodejs/Product/Nodejs/Resources.resx b/Nodejs/Product/Nodejs/Resources.resx index b72bcd3ec..75e2ae257 100644 --- a/Nodejs/Product/Nodejs/Resources.resx +++ b/Nodejs/Product/Nodejs/Resources.resx @@ -556,6 +556,12 @@ We recommend installing the latest version of Microsoft Azure Tools for Visual S Your version of Microsoft Azure Tools is not supported by Node.js Tools for Visual Studio. + + Could Not Clear Node.js Cache Directory + + + Could not clear Node.js cache directory. Try manually deleting the directory: {0} + &Continue Some manual steps will be required to configure your project. From 138a3ada3f8720e14d23b803a0e063ff5278817f Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jun 2016 17:16:37 -0700 Subject: [PATCH 3/4] Handle long paths for typings install --- .../Nodejs/Options/NodejsNpmOptionsControl.cs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs index 58e9c6778..2bb90701b 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs @@ -15,6 +15,7 @@ //*********************************************************// using System; +using System.Diagnostics; using System.IO; using System.Windows.Forms; using Microsoft.NodejsTools.Project; @@ -51,12 +52,31 @@ private void ClearCacheButton_Click(object sender, EventArgs e) { } private static bool TryDeleteCacheDirectory(string cachePath) { - try { - Directory.Delete(cachePath, true); - return true; - } catch (DirectoryNotFoundException) { - // Directory has already been deleted. Do nothing. + if (!Directory.Exists(cachePath)) { return true; + } + + try { + // To handle long paths, nuke the directory contents with robocopy + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + var psi = new ProcessStartInfo("cmd.exe", string.Format(@"/C robocopy /mir ""{0}"" ""{1}""", tempDirectory, cachePath)) { + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(psi)) { + process.WaitForExit(2000); + } + + // Then delete the directory itself + try { + Directory.Delete(cachePath); + } catch (DirectoryNotFoundException) { + // noop + } + + return !Directory.Exists(cachePath); } catch (IOException) { // files are in use or path is too long return false; From 1ebb190a6b54fd9cbdff112e9329e6bb1a8d7729 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jun 2016 17:28:01 -0700 Subject: [PATCH 4/4] Small reliability additions --- Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs index 2bb90701b..5cfb86b6e 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs @@ -66,12 +66,12 @@ private static bool TryDeleteCacheDirectory(string cachePath) { }; using (var process = Process.Start(psi)) { - process.WaitForExit(2000); + process.WaitForExit(10000); } // Then delete the directory itself try { - Directory.Delete(cachePath); + Directory.Delete(cachePath, true); } catch (DirectoryNotFoundException) { // noop }