Last active
February 5, 2018 01:05
-
-
Save peterhuene/a95b370f8bf84ee4b68be7b94de628fe to your computer and use it in GitHub Desktop.
Transactional tool install
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Transactions; | |
using Microsoft.Extensions.EnvironmentAbstractions; | |
namespace Microsoft.DotNet.ToolPackage | |
{ | |
internal interface IToolPackageManager | |
{ | |
(ToolConfiguration Configuration, string ExecutablePath) InstallPackage( | |
string packageId, | |
string packageVersion = null, | |
string targetFramework = null, | |
FilePath? tempProjectPath = null, | |
DirectoryPath? offlineFeedPath = null, | |
FilePath? nugetConfig = null, | |
string source = null, | |
string verbosity = null); | |
ToolConfiguration UninstallPackage(string packageId); | |
} | |
internal interface IShellShimManager | |
{ | |
void CreateShim(FilePath targetExecutablePath, string commandName); | |
void RemoveShim(string commandName); | |
} | |
internal class TransactionalOperation : IEnlistmentNotification | |
{ | |
private Action _undo; | |
private Action _prepare; | |
public TransactionalOperation(Action undo, Action prepare) | |
{ | |
_undo = undo; | |
_prepare = prepare; | |
} | |
public static void Enlist(Action undo = null, Action prepare = null) | |
{ | |
if (Transaction.Current == null) | |
{ | |
throw new InvalidOperationException(); | |
} | |
if (undo == null && prepare == null) | |
{ | |
return; | |
} | |
Transaction.Current.EnlistVolatile(new TransactionalOperation(undo, prepare), EnlistmentOptions.None); | |
} | |
public void Commit(Enlistment enlistment) | |
{ | |
_undo = _prepare = null; | |
enlistment.Done(); | |
} | |
public void InDoubt(Enlistment enlistment) | |
{ | |
Rollback(enlistment); | |
} | |
public void Prepare(PreparingEnlistment preparingEnlistment) | |
{ | |
try | |
{ | |
Prepare(); | |
} | |
catch (Exception ex) | |
{ | |
_prepare = null; | |
Undo(); | |
preparingEnlistment.ForceRollback(ex); | |
return; | |
} | |
preparingEnlistment.Prepared(); | |
} | |
public void Rollback(Enlistment enlistment) | |
{ | |
_prepare = null; | |
Undo(); | |
enlistment.Done(); | |
} | |
private void Prepare() | |
{ | |
if (_prepare != null) | |
{ | |
_prepare(); | |
} | |
_prepare = null; | |
} | |
private void Undo() | |
{ | |
if (_undo != null) | |
{ | |
_undo(); | |
} | |
_undo = null; | |
} | |
} | |
internal class ToolPackageManager : IToolPackageManager | |
{ | |
private readonly DirectoryPath _toolsPath; | |
private readonly DirectoryPath _stagingPath; | |
public ToolPackageManager(DirectoryPath toolsPath, DirectoryPath stagingPath) | |
{ | |
_toolsPath = toolsPath; | |
_stagingPath = stagingPath; | |
} | |
public (ToolConfiguration Configuration, string ExecutablePath) InstallPackage( | |
string packageId, | |
string packageVersion = null, | |
string targetFramework = null, | |
FilePath? tempProjectPath = null, | |
DirectoryPath? offlineFeedPath = null, | |
FilePath? nugetConfig = null, | |
string source = null, | |
string verbosity = null) | |
{ | |
var stagedPackageId = Path.GetRandomFileName(); | |
var stagedPackageDirectory = GetStagedPackageDirectory(stagedPackageId); | |
var packageDirectory = GetPackageDirectory(packageId); | |
// Omitted: check to see if the package already exists | |
// Create staging directory | |
Directory.CreateDirectory(stagedPackageDirectory); | |
// Create a local transaction scope that inherits from any outter scope | |
// This will commit the transaction immediately if there is no outter scope | |
// If there is an outter scope, the completion of the inner scope will have no effect | |
using (var scope = new TransactionScope()) | |
{ | |
TransactionalOperation.Enlist( | |
undo: () => { | |
if (Directory.Exists(stagedPackageDirectory)) | |
{ | |
Directory.Delete(stagedPackageDirectory, true); | |
} | |
if (Directory.Exists(packageDirectory)) | |
{ | |
Directory.Delete(packageDirectory, true); | |
} | |
}, | |
prepare: () => Directory.Move(stagedPackageDirectory, packageDirectory)); | |
// Omitted: create the temp project and restore into the staged directory | |
// Omitted: read the tool configuration from the staged location | |
ToolConfiguration configuration = null; | |
// Omitted: calculate the executable path based upon the expected location in the non-staged package directory | |
string executablePath = null; | |
scope.Complete(); | |
return (configuration, executablePath); | |
} | |
} | |
public ToolConfiguration UninstallPackage(string packageId) | |
{ | |
throw new NotImplementedException(); | |
} | |
private string GetStagedPackageDirectory(string stagedPackageId) | |
{ | |
return Path.Combine(_stagingPath.Value, stagedPackageId); | |
} | |
private string GetPackageDirectory(string packageId) | |
{ | |
return Path.Combine(_toolsPath.Value, packageId); | |
} | |
} | |
public class ShellShimManager : IShellShimManager | |
{ | |
DirectoryPath _shimsDirectory; | |
public ShellShimManager(DirectoryPath shimsDirectory) | |
{ | |
_shimsDirectory = shimsDirectory; | |
} | |
public void CreateShim(FilePath targetExecutablePath, string commandName) | |
{ | |
// Omitted: checking if the shim already exists | |
// Create a local transaction scope that inherits from any outter scope | |
// This will commit the transaction immediately if there is no outter scope | |
// If there is an outter scope, the completion of the inner scope will have no effect | |
using (var scope = new TransactionScope()) | |
{ | |
// Omitted: create the shim files | |
string pathToShimFile = null; | |
TransactionalOperation.Enlist(undo: () => File.Delete(pathToShimFile)); | |
// Omitted: adding an operation to delete the .exe.config on Windows | |
scope.Complete(); | |
} | |
} | |
public void RemoveShim(string commandName) | |
{ | |
throw new NotImplementedException(); | |
} | |
} | |
public class Example | |
{ | |
public static void Run() | |
{ | |
bool somethingBadHappens = true; | |
using (var scope = new TransactionScope()) | |
{ | |
var packageManager = new ToolPackageManager(new DirectoryPath("tools"), new DirectoryPath("staging")); | |
var shimManager = new ShellShimManager(new DirectoryPath("shims")); | |
// Stages the install of the tool, waiting for the transaction to commit | |
var (configuration, executablePath) = packageManager.InstallPackage("some.tool"); | |
// Create the shim | |
shimManager.CreateShim(new FilePath(executablePath), configuration.CommandName); | |
if (somethingBadHappens) | |
{ | |
// This will roll back the package install and the creation of the shim | |
throw new Exception("whoops"); | |
} | |
scope.Complete(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment