Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Nodejs/Product/Nodejs/NodejsConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
//*********************************************************//

using System;
using System.IO;

namespace Microsoft.NodejsTools {
internal class NodejsConstants {
Expand Down Expand Up @@ -73,6 +74,33 @@ internal class NodejsConstants {

internal const string NodeToolsProcessIdEnvironmentVariable = "_NTVS_PID";

public static string NtvsLocalAppData {
get {
return Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"Node.js Tools");
}
}

/// <summary>
/// Path to the private package where NTVS tools are installed.
/// </summary>
public static string ExternalToolsPath {
get {
return Path.Combine(NtvsLocalAppData, "ExternalTools");
}

}
/// <summary>
/// Path to where NTVS caches Npm data.
/// </summary>
public static string NpmCachePath {
get {
return Path.Combine(NtvsLocalAppData, "NpmCache");
}
}

/// <summary>
/// Checks whether a relative and double-backslashed seperated path contains a folder name.
/// </summary>
Expand Down
68 changes: 43 additions & 25 deletions Nodejs/Product/Nodejs/Options/NodejsNpmOptionsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,20 @@
//*********************************************************//

using System;
using System.Diagnostics;
using System.IO;
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;
}

Expand All @@ -42,34 +37,57 @@ internal void SyncPageWithControlSettings(NodejsNpmOptionsPage page) {
}

private void ClearCacheButton_Click(object sender, EventArgs e) {
bool didClearNpmCache = TryDeleteCacheDirectory(NodejsConstants.NpmCachePath);
bool didClearTools = TryDeleteCacheDirectory(NodejsConstants.ExternalToolsPath);

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 TryDeleteCacheDirectory(string cachePath) {
if (!Directory.Exists(cachePath)) {
return true;
}

try {
Directory.Delete(_npmCachePath, true);
_cacheClearedSuccessfully.Visible = true;
} catch (DirectoryNotFoundException) {
// Directory has already been deleted. Do nothing.
_cacheClearedSuccessfully.Visible = true;
} catch (IOException exception) {
// To handle long paths, nuke the directory contents with robocopy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: nuke -> exterminate 🤖

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(10000);
}

// Then delete the directory itself
try {
Directory.Delete(cachePath, true);
} catch (DirectoryNotFoundException) {
// noop
}

return !Directory.Exists(cachePath);
} catch (IOException) {
// files are in use or path is too long
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, just now realizing this case might be pretty likely in the case of typings... what's the max path length of typings?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here was the script I used:

C:\Users\matb.REDMOND\AppData\Local\Microsoft\Node.js Tools> Get-ChildItem -Recurse . | % { $_.fullname } | sort -Property Length

Longest path is 150 characters with Node 6 and my relatively short user name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On < Node 6, the max path was 273 :sad:

I changed the logic to use robocopy to nuke the directory instead. This was tested with Node 6 and Node 0.12, and both seem to work fine now.

For reference, another way to delete long path files is with the Windows DeleteFile function:

In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\?" to the path.

MessageBox.Show(
string.Format("Cannot clear npm cache. {0}", exception.Message),
"Cannot Clear npm Cache",
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 npm cache. Try manually deleting the directory: {0}", _npmCachePath),
"Cannot Clear npm Cache",
MessageBoxButtons.OK,
MessageBoxIcon.Information
);
}
return false;
}
}
}
13 changes: 0 additions & 13 deletions Nodejs/Product/Nodejs/Options/NodejsNpmOptionsPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
//
//*********************************************************//

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;

Expand Down Expand Up @@ -47,17 +45,6 @@ protected override IWin32Window Window {
/// </summary>
public bool ShowOutputWindowWhenExecutingNpm { get; set; }

public string NpmCachePath {
get {
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"Node.js Tools",
"NpmCache"
);
}
}

/// <summary>
/// Resets settings back to their defaults. This should be followed by
/// a call to <see cref="SaveSettingsToStorage" /> to commit the new
Expand Down
2 changes: 1 addition & 1 deletion Nodejs/Product/Nodejs/Project/NodeModulesNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions Nodejs/Product/Nodejs/Project/ProjectResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
6 changes: 6 additions & 0 deletions Nodejs/Product/Nodejs/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,12 @@ We recommend installing the latest version of Microsoft Azure Tools for Visual S
<data name="AzureToolsUpgradeRecommended" xml:space="preserve">
<value>Your version of Microsoft Azure Tools is not supported by Node.js Tools for Visual Studio.</value>
</data>
<data name="CacheDirectoryClearFailedTitle" xml:space="preserve">
<value>Could Not Clear Node.js Cache Directory</value>
</data>
<data name="CacheDirectoryClearFailedCaption" xml:space="preserve">
<value>Could not clear Node.js cache directory. Try manually deleting the directory: {0}</value>
</data>
<data name="ContinueWithoutAzureToolsUpgrade" xml:space="preserve">
<value>&amp;Continue
Some manual steps will be required to configure your project.</value>
Expand Down
19 changes: 3 additions & 16 deletions Nodejs/Product/Nodejs/TypingsAcquisition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,13 @@ internal class TypingsAcquisition {

private static SemaphoreSlim typingsToolGlobalWorkSemaphore = new SemaphoreSlim(1);

/// <summary>
/// Path the the private package where the typings acquisition tool is installed.
/// </summary>
private static string NtvsExternalToolsPath {
get {
return Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Microsoft",
"Node.js Tools",
"ExternalTools");
}
}

/// <summary>
/// Full path to the typings acquisition tool.
/// </summary>
private static string TypingsToolPath {
get {
return Path.Combine(
NtvsExternalToolsPath,
NodejsConstants.ExternalToolsPath,
"node_modules",
".bin",
TypingsToolExe);
Expand Down Expand Up @@ -161,11 +148,11 @@ private async Task<string> EnsureTypingsToolInstalled() {
private async Task<bool> 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);
}
}

Expand Down