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
38 changes: 38 additions & 0 deletions dev/pyRevitLabs/pyRevitLabs.Common/CommonUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,44 @@ public static bool VerifyPath(string path) {
return false;
}

/// <summary>
/// Expands %VAR% tokens in a user-supplied path (e.g. --dest=%LOCALAPPDATA%\pyRevit).
/// Repeats until stable so nested values like TEMP=%LOCALAPPDATA%\Temp resolve fully.
/// </summary>
public static string ExpandEnvironmentPath(string path) {
if (string.IsNullOrWhiteSpace(path))
return path;
return ExpandEnvironmentPathRecursive(path.Trim());
}

/// <summary>
/// User temp directory with environment variables expanded (handles TEMP=%LOCALAPPDATA%\Temp).
/// </summary>
public static string GetUserTempDirectory() {
var temp = Environment.GetEnvironmentVariable("TEMP");
var candidate = !string.IsNullOrWhiteSpace(temp)
? ExpandEnvironmentPathRecursive(temp.Trim())
: ExpandEnvironmentPathRecursive("%TEMP%");
if (IsConcreteExpandedPath(candidate))
return candidate;
return Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}

private static bool IsConcreteExpandedPath(string path) {
return !string.IsNullOrWhiteSpace(path) && path.IndexOf('%') < 0;
}

private static string ExpandEnvironmentPathRecursive(string path) {
var expanded = path;
for (int pass = 0; pass < 8; pass++) {
var next = Environment.ExpandEnvironmentVariables(expanded).Trim();
if (string.Equals(next, expanded, StringComparison.OrdinalIgnoreCase))
break;
expanded = next;
}
return expanded;
}

public static bool VerifyPythonScript(string path) {
return VerifyFile(path) && path.ToLower().EndsWith(".py");
}
Expand Down
2 changes: 1 addition & 1 deletion dev/pyRevitLabs/pyRevitLabs.Common/UserEnv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public static bool IsRunAsAdmin() {

public static string UserHome => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);

public static string UserTemp => Environment.ExpandEnvironmentVariables("%TEMP%");
public static string UserTemp => CommonUtils.GetUserTempDirectory();

private static string[] _knownFolderGuids = new string[]
{
Expand Down
19 changes: 16 additions & 3 deletions dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitClones.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ public static void DeployFromRepo(string cloneName,
string destPath = null,
GitInstallerCredentials credentials = null)
{
if (destPath != null)
destPath = CommonUtils.ExpandEnvironmentPath(destPath);

string repoSourcePath = repoUrl ?? PyRevitLabsConsts.OriginalRepoGitPath;
if (!repoSourcePath.IsValidHttpUrl())
repoSourcePath = CommonUtils.ExpandEnvironmentPath(repoSourcePath);
string repoBranch = branchName != null ? branchName : PyRevitLabsConsts.TargetBranch;
logger.Debug("Repo source determined as \"{0}:{1}\"", repoSourcePath, repoBranch);

Expand Down Expand Up @@ -309,6 +314,11 @@ public static void DeployFromImage(string cloneName,
bool installBinaries = true,
BinArtifactInstallMode binInstallMode = BinArtifactInstallMode.Clone)
{
if (destPath != null)
destPath = CommonUtils.ExpandEnvironmentPath(destPath);
if (imagePath != null)
imagePath = CommonUtils.ExpandEnvironmentPath(imagePath);

string repoBranch = branchName != null ? branchName : PyRevitLabsConsts.TargetBranch;
string imageSource = imagePath != null ? imagePath : GithubAPI.GetBranchArchiveUrl(PyRevitLabsConsts.OriginalRepoId, repoBranch);
string imageFilePath = null;
Expand Down Expand Up @@ -342,7 +352,7 @@ public static void DeployFromImage(string cloneName,
{
try
{
var pkgDest = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), Path.GetFileName(imageSource));
var pkgDest = Path.Combine(CommonUtils.GetUserTempDirectory(), Path.GetFileName(imageSource));
logger.Info("Downloading package \"{0}\"", imageSource);
logger.Debug("Downloading package \"{0}\" to \"{1}\"", imageSource, pkgDest);
imageFilePath =
Expand Down Expand Up @@ -375,7 +385,7 @@ public static void DeployFromImage(string cloneName,
);
}
var stagedImage = Path.Combine(
Environment.GetEnvironmentVariable("TEMP"),
CommonUtils.GetUserTempDirectory(),
Path.GetFileNameWithoutExtension(imageFilePath)
);

Expand Down Expand Up @@ -481,7 +491,10 @@ public static void DeployFromImage(string cloneName,
}
catch (PyRevitException ex)
{
logger.Error("Can not find a valid clone inside extracted package. | {0}", ex.Message);
var errMsg = ex.Message;
if (errMsg != null && errMsg.IndexOf('%') >= 0)
errMsg += " Path may contain unexpanded environment variables; ensure TEMP and --dest resolve to absolute paths.";
logger.Error("Can not find a valid clone inside extracted package. | {0}", errMsg);
}
}

Expand Down
2 changes: 1 addition & 1 deletion dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ private static List<PyRevitExtensionDefinition> LookupExtensionInDefinitionFile(
try {
filePath =
CommonUtils.DownloadFile(fileOrUri,
Path.Combine(Environment.GetEnvironmentVariable("TEMP"),
Path.Combine(CommonUtils.GetUserTempDirectory(),
PyRevitConsts.EnvConfigsExtensionDBFileName)
);
}
Expand Down
2 changes: 1 addition & 1 deletion dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public PyRevitRunnerExecEnv(PyRevitAttachment attachment) {
// generate unique id for this execution
ExecutionId = Guid.NewGuid().ToString();
// setup working dir
WorkingDirectory = Path.Combine(Environment.GetEnvironmentVariable("TEMP"), ExecutionId);
WorkingDirectory = Path.Combine(CommonUtils.GetUserTempDirectory(), ExecutionId);
CommonUtils.EnsurePath(WorkingDirectory);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System;
using System.IO;
using System.IO.Compression;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using pyRevitLabs.Common;
using pyRevitLabs.PyRevit;

namespace pyRevitLabs.UnitTests {
[TestClass]
public class CommonUtilsPathTests {
private string _tempRoot;
private string _previousPyRevitPathOverride;
private string _previousTemp;
private string _previousLocalAppData;

[TestInitialize]
public void Setup() {
_tempRoot = Path.Combine(Path.GetTempPath(), "pyRevitPathTests_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_tempRoot);
_previousPyRevitPathOverride = Environment.GetEnvironmentVariable(PyRevitLabsConsts.PyRevitPathOverrideEnvVar);
_previousTemp = Environment.GetEnvironmentVariable("TEMP");
_previousLocalAppData = Environment.GetEnvironmentVariable("LOCALAPPDATA");
Environment.SetEnvironmentVariable(
PyRevitLabsConsts.PyRevitPathOverrideEnvVar,
Path.Combine(_tempRoot, "AppDataPyRevit"));
Directory.CreateDirectory(PyRevitLabsConsts.PyRevitPath);
}

[TestCleanup]
public void Cleanup() {
Environment.SetEnvironmentVariable(
PyRevitLabsConsts.PyRevitPathOverrideEnvVar,
_previousPyRevitPathOverride);
Environment.SetEnvironmentVariable("TEMP", _previousTemp);
Environment.SetEnvironmentVariable("LOCALAPPDATA", _previousLocalAppData);
if (Directory.Exists(_tempRoot))
Directory.Delete(_tempRoot, recursive: true);
}

[TestMethod]
public void ExpandEnvironmentPath_ExpandsNestedVariables() {
Environment.SetEnvironmentVariable("LOCALAPPDATA", _tempRoot);
var expanded = CommonUtils.ExpandEnvironmentPath("%LOCALAPPDATA%\\pyRevit");
Assert.AreEqual(Path.Combine(_tempRoot, "pyRevit"), expanded);
}

[TestMethod]
public void GetUserTempDirectory_ExpandsPercentTemp() {
Environment.SetEnvironmentVariable("LOCALAPPDATA", _tempRoot);
Environment.SetEnvironmentVariable("TEMP", "%LOCALAPPDATA%\\Temp");
var tempDir = CommonUtils.GetUserTempDirectory();
Assert.IsFalse(tempDir.Contains("%"), "Temp path should not contain unexpanded variables.");
StringAssert.StartsWith(tempDir, _tempRoot);
Assert.IsTrue(tempDir.EndsWith("Temp", StringComparison.OrdinalIgnoreCase));
}

[TestMethod]
public void GetUserTempDirectory_FallsBackToPathGetTempPathWhenUnexpanded() {
Environment.SetEnvironmentVariable("TEMP", "%NONEXISTENT_PYREVIT_VAR%\\Temp");
var tempDir = CommonUtils.GetUserTempDirectory();
Assert.IsFalse(tempDir.Contains("%"));
var expected = Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
Assert.AreEqual(expected, tempDir);
}

[TestMethod]
public void UserTemp_MatchesGetUserTempDirectory() {
Environment.SetEnvironmentVariable("LOCALAPPDATA", _tempRoot);
Environment.SetEnvironmentVariable("TEMP", "%LOCALAPPDATA%\\Temp");
Assert.AreEqual(CommonUtils.GetUserTempDirectory(), UserEnv.UserTemp);
}

[TestMethod]
public void DeployFromImage_UsesExpandedTempForStaging() {
var localAppData = Path.Combine(_tempRoot, "LocalAppData");
Directory.CreateDirectory(localAppData);
Environment.SetEnvironmentVariable("LOCALAPPDATA", localAppData);
Environment.SetEnvironmentVariable("TEMP", "%LOCALAPPDATA%\\Temp");

var zipPath = Path.Combine(_tempRoot, "testclone.zip");
var destPath = Path.Combine(_tempRoot, "cloneDest");
var cloneName = "TempDeployTest";
CreateMinimalCloneZip(zipPath);

try {
PyRevitClones.DeployFromImage(
cloneName: cloneName,
deploymentName: "core",
branchName: null,
imagePath: zipPath,
destPath: destPath,
installBinaries: false);

Assert.IsTrue(Directory.Exists(Path.Combine(destPath, "bin")),
"core deployment should copy bin when TEMP is expanded.");
Assert.IsTrue(Directory.Exists(Path.Combine(destPath, "pyrevitlib", "pyrevit")),
"core deployment should copy pyrevitlib when TEMP is expanded.");

var expandedTemp = CommonUtils.GetUserTempDirectory();
Assert.IsFalse(expandedTemp.Contains("%"));
Assert.IsTrue(Directory.Exists(expandedTemp),
"Expanded temp directory should exist after staging.");
}
finally {
try {
var clone = PyRevitClones.GetRegisteredClone(cloneName);
PyRevitClones.UnregisterClone(clone);
}
catch {
// clone may not have registered if deploy failed
}
if (Directory.Exists(destPath))
CommonUtils.DeleteDirectory(destPath);
}
}

private static void CreateMinimalCloneZip(string zipPath) {
var root = Path.Combine(Path.GetDirectoryName(zipPath), "zipcontent");
if (Directory.Exists(root))
Directory.Delete(root, recursive: true);
Directory.CreateDirectory(Path.Combine(root, "bin"));
Directory.CreateDirectory(Path.Combine(root, "pyrevitlib", "pyrevit"));
Directory.CreateDirectory(Path.Combine(root, "site-packages"));
File.WriteAllText(
Path.Combine(root, "pyRevitfile"),
"[deployments]\r\ncore = ['bin', 'pyrevitlib', 'site-packages', 'pyRevitfile']\r\n");
if (File.Exists(zipPath))
File.Delete(zipPath);
ZipFile.CreateFromDirectory(root, zipPath);
Directory.Delete(root, recursive: true);
}
}
}