mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
354 lines
12 KiB
C#
354 lines
12 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
#nullable disable
|
|
|
|
using System;
|
|
using System.Text;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Management
|
|
{
|
|
/// <summary>
|
|
/// defines mandatory interface that an action must implement
|
|
/// </summary>
|
|
public interface IManagementAction
|
|
{
|
|
ExecutionMode LastExecutionResult { get; }
|
|
|
|
/// <summary>
|
|
/// performs custom action when user requests to cancel execution.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
void OnCancel(object sender);
|
|
|
|
/// <summary>
|
|
/// Overridable function that allow a derived class to implement its
|
|
/// OnScript functionality.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <returns>text of the generated script</returns>
|
|
string OnScript(object sender);
|
|
|
|
/// <summary>
|
|
/// Overridable function that allow a derived class to implement its
|
|
/// OnRunNow functionality
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
void OnRunNow(object sender);
|
|
|
|
/// <summary>
|
|
/// Overridable function that allow a derived class to implement
|
|
/// a finalizing action after a RunNow or RunNowAndClose were sucesfully executed
|
|
/// NOTE: same as OnGatherUiInformation, this method is always called from UI thread
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
void OnTaskCompleted(object sender, ExecutionMode executionMode, RunType executionType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This class is responsible for executing panels one by one.
|
|
/// It is reused by ViewSwitcherControlsManager and treepanelform classes
|
|
/// </summary>
|
|
internal class ExecutionHandlerDelegate
|
|
{
|
|
private object cancelCriticalSection = new object();
|
|
private IManagementAction managementAction;
|
|
|
|
public ExecutionHandlerDelegate(IManagementAction managementAction)
|
|
{
|
|
this.managementAction = managementAction;
|
|
}
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <param name="runType"></param>
|
|
/// <param name="sender"></param>
|
|
/// <returns>execution result</returns>
|
|
public ExecutionMode Run(RunType runType, object sender)
|
|
{
|
|
//dispatch the call to the right method
|
|
switch (runType)
|
|
{
|
|
case RunType.RunNow:
|
|
this.managementAction.OnRunNow(sender);
|
|
break;
|
|
|
|
case RunType.ScriptToWindow:
|
|
this.managementAction.OnScript(sender);
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(SR.UnexpectedRunType);
|
|
}
|
|
|
|
if((this.managementAction.LastExecutionResult == ExecutionMode.Failure) ||
|
|
(this.managementAction.LastExecutionResult == ExecutionMode.Cancel))
|
|
{
|
|
return this.managementAction.LastExecutionResult;
|
|
}
|
|
|
|
// if we're here, everything went fine
|
|
return ExecutionMode.Success;
|
|
}
|
|
|
|
/// <summary>
|
|
/// performs custom action wen user requests a cancel
|
|
/// this is called from the UI thread
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
public void Cancel(object sender)
|
|
{
|
|
lock (this.cancelCriticalSection)
|
|
{
|
|
this.managementAction.OnCancel(sender);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// manager that hooks up tree view with the individual views
|
|
/// </summary>
|
|
internal sealed class ExecutionHandler : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// handler that we delegate execution related tasks to
|
|
/// </summary>
|
|
private ExecutionHandlerDelegate executionHandlerDelegate;
|
|
|
|
/// <summary>
|
|
/// class that describes available views that is also aware of execution
|
|
/// </summary>
|
|
private IExecutionAwareManagementAction managementAction;
|
|
|
|
/// <summary>
|
|
/// result of the last execution
|
|
/// </summary>
|
|
private ExecutionMode executionResult;
|
|
|
|
/// <summary>
|
|
/// exception that caused execution failure
|
|
/// </summary>
|
|
private Exception executionFailureException;
|
|
|
|
/// <summary>
|
|
/// text of the generated script if RunNow method was called last time with scripting option
|
|
/// </summary>
|
|
private StringBuilder script;
|
|
|
|
/// <summary>
|
|
/// creates instance of the class and returns service provider that aggregates the provider
|
|
/// provider with extra services
|
|
/// </summary>
|
|
/// <param name="serviceProvider">service provider from the host</param>
|
|
/// <param name="aggregatedProvider">
|
|
/// aggregates service provider that is derived from the host service provider and
|
|
/// is extended with extra services and/or overriden services. The host should be
|
|
/// using this provider whenever it has to specify an IServiceProvider to a component
|
|
/// that will be managed by this class
|
|
/// </param>
|
|
public ExecutionHandler(IExecutionAwareManagementAction managementAction)
|
|
{
|
|
this.managementAction = managementAction;
|
|
this.executionHandlerDelegate = new ExecutionHandlerDelegate(managementAction);
|
|
}
|
|
|
|
#region public interface
|
|
|
|
public ExecutionMode ExecutionResult
|
|
{
|
|
get
|
|
{
|
|
return this.executionResult;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// exception that caused execution failure
|
|
/// </summary>
|
|
public Exception ExecutionFailureException
|
|
{
|
|
get
|
|
{
|
|
return this.executionFailureException;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// text of the generated script if RunNow method was called last time with scripting option
|
|
/// </summary>
|
|
public string ScriptTextFromLastRun
|
|
{
|
|
get
|
|
{
|
|
if (this.script != null)
|
|
{
|
|
return this.script.ToString();
|
|
}
|
|
else
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// we call the run now implementaion of the management action.
|
|
/// If any exception is generated we stop the execution and we set the execution mode flag to failure.
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
public void RunNow(RunType runType, object sender)
|
|
{
|
|
try
|
|
{
|
|
// reset some internal vars
|
|
this.executionResult = ExecutionMode.Failure;
|
|
|
|
// ensure that we have valid StringBulder for scripting
|
|
if (IsScripting(runType))
|
|
{
|
|
EnsureValidScriptBuilder();
|
|
}
|
|
|
|
// do preprocess action. It is possible to do entire execution from inside this method
|
|
if (this.managementAction != null)
|
|
{
|
|
PreProcessExecutionInfo preProcessInfo = new PreProcessExecutionInfo(runType);
|
|
if (!this.managementAction.PreProcessExecution(preProcessInfo, out this.executionResult))
|
|
{
|
|
// In case of scripting preProcessInfo.Script must contain text of the script
|
|
if (executionResult == ExecutionMode.Success && IsScripting(runType) && preProcessInfo.Script != null)
|
|
{
|
|
this.script.Append(preProcessInfo.Script);
|
|
}
|
|
|
|
return; // result of execution is in executionResult
|
|
}
|
|
}
|
|
// NOTE: post process action is done in finally block below
|
|
|
|
// start executing
|
|
this.executionResult = this.executionHandlerDelegate.Run(runType, sender);
|
|
}
|
|
#region error handling
|
|
|
|
catch (OutOfMemoryException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (System.Threading.ThreadAbortException)
|
|
{
|
|
throw;
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
this.executionResult = ExecutionMode.Cancel;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
ProcessExceptionDuringExecution(e);
|
|
|
|
return;
|
|
}
|
|
finally
|
|
{
|
|
//do postprocess action
|
|
if (this.managementAction != null)
|
|
{
|
|
this.managementAction.PostProcessExecution(runType, this.executionResult);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kicks off Cancel operation
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
public void InitiateCancel(object sender)
|
|
{
|
|
if (this.managementAction != null)
|
|
{
|
|
if (!this.managementAction.Cancel())
|
|
{
|
|
//everything was done inside this method
|
|
this.executionResult = ExecutionMode.Cancel;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//otherwise do cancel ourselves
|
|
// if everything goes OK, Run() method will return with Cancel result
|
|
this.executionHandlerDelegate.Cancel(sender);
|
|
}
|
|
|
|
/// <summary>
|
|
/// is called by the host to do post execution actions
|
|
/// </summary>
|
|
/// <param name="sender"></param>
|
|
/// <param name="executionMode"></param>
|
|
/// <param name="executionType"></param>
|
|
public void OnTaskCompleted(object sender, ExecutionMode executionResult, RunType executionType)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// enables deterministic cleanup
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
IDisposable managementActionAsDisposable = this.managementAction as IDisposable;
|
|
if (managementActionAsDisposable != null)
|
|
{
|
|
managementActionAsDisposable.Dispose();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region private helpers
|
|
|
|
/// <summary>
|
|
/// determines whether given run type corresponds to scripting or not
|
|
/// </summary>
|
|
/// <param name="runType"></param>
|
|
/// <returns></returns>
|
|
private bool IsScripting(RunType runType)
|
|
{
|
|
return (runType == RunType.ScriptToClipboard ||
|
|
runType == RunType.ScriptToFile ||
|
|
runType == RunType.ScriptToWindow ||
|
|
runType == RunType.ScriptToJob);
|
|
}
|
|
|
|
/// <summary>
|
|
/// ensure that we have valid StringBulder for scripting
|
|
/// </summary>
|
|
private void EnsureValidScriptBuilder()
|
|
{
|
|
if (this.script == null)
|
|
{
|
|
this.script = new StringBuilder(256);
|
|
}
|
|
else
|
|
{
|
|
this.script.Length = 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// helper function that is called when we caught an exception during execution
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
private void ProcessExceptionDuringExecution(Exception ex)
|
|
{
|
|
// show the error
|
|
this.executionResult = ExecutionMode.Failure;
|
|
this.executionFailureException = ex;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|