mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-15 02:48:35 -05:00
SQL Agent configuration for Operators, Alerts and Proxies (WIP) (#621)
* Initial non-refactored SQL Agent alert classes (WIP) * Move agent classes into subdirectories * Refactor the agent config code a bit more * Add more implementation for handlers * Add more code to the create alert handler * Clean up agent alert class * Clean up alert methods a bit * Initial Operator contracts * Additonal SQL Agent config changes * More Proxy config cleanup * Cleanup AgentProxy class * Additional cleanups * Run SRGen * Add security service to create credential objects
This commit is contained in:
976
src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentActions.cs
Normal file
976
src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentActions.cs
Normal file
@@ -0,0 +1,976 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Xml;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Diagnostics;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Smo.Agent;
|
||||
using Microsoft.SqlServer.Management.UI;
|
||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
|
||||
#region AgentAction class
|
||||
|
||||
/// <summary>
|
||||
/// Main class all af the "immediate" agent actions derive from. These actions execute immediately
|
||||
/// are not scriptable. We use the progress reporting dialog to give the user feedback on progress
|
||||
/// etc.
|
||||
/// </summary>
|
||||
internal abstract class AgentAction
|
||||
{
|
||||
#region private members
|
||||
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected Microsoft.SqlServer.Management.Smo.Server smoServer = null;
|
||||
protected IManagedConnection managedConnection;
|
||||
protected Urn[] urnParameters;
|
||||
protected STParameters param = null;
|
||||
protected ProgressItemCollection actions = new ProgressItemCollection();
|
||||
|
||||
#endregion
|
||||
|
||||
protected object ActionObject;
|
||||
|
||||
#region construction
|
||||
|
||||
public AgentAction(XmlDocument document, IServiceProvider source)
|
||||
: this(document, source, null)
|
||||
{
|
||||
}
|
||||
|
||||
public AgentAction(XmlDocument document, IServiceProvider source, object actionObject)
|
||||
{
|
||||
// parameter check
|
||||
if (document == null)
|
||||
{
|
||||
throw new ArgumentNullException("document");
|
||||
}
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException("source");
|
||||
}
|
||||
|
||||
if (actionObject != null)
|
||||
{
|
||||
this.ActionObject = actionObject;
|
||||
}
|
||||
|
||||
// get the managed connection
|
||||
managedConnection = source.GetService(typeof (IManagedConnection)) as IManagedConnection;
|
||||
|
||||
// get the connection
|
||||
SqlOlapConnectionInfoBase ci = managedConnection.Connection;
|
||||
// get the server connection
|
||||
ServerConnection serverConnection =
|
||||
((SqlConnectionInfoWithConnection) managedConnection.Connection).ServerConnection;
|
||||
|
||||
smoServer = new Microsoft.SqlServer.Management.Smo.Server(serverConnection);
|
||||
|
||||
// get the list or urn's that have been passed in
|
||||
param = new STParameters(document);
|
||||
StringCollection urnStrings = new StringCollection();
|
||||
|
||||
// get a list of urns that have been passed in.
|
||||
param.GetParam("urn", urnStrings);
|
||||
|
||||
// store the Urn's as real Urns
|
||||
urnParameters = new Urn[urnStrings.Count];
|
||||
for (int i = 0; i < urnStrings.Count; i++)
|
||||
{
|
||||
urnParameters[i] = new Urn(urnStrings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnLoad(EventArgs e)
|
||||
{
|
||||
// ask derived classes to build a list of actions to be
|
||||
// performed. Do not remove this call from OnLoad method!
|
||||
GenerateActions();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region cleanup
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (components != null)
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.managedConnection != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
this.managedConnection.Close();
|
||||
}
|
||||
this.managedConnection = null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region abstract methods
|
||||
|
||||
/// <summary>
|
||||
/// Generate the actions the dialog will perform. Derived classes should add
|
||||
/// IAction based actions to the actions collection.
|
||||
/// </summary>
|
||||
protected abstract void GenerateActions();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enable Alerts
|
||||
|
||||
/// <summary>
|
||||
/// Enables one or more alerts.
|
||||
/// </summary>
|
||||
internal class EnableAgentAlerts : AgentAction
|
||||
{
|
||||
public EnableAgentAlerts(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Alert alert = this.smoServer.GetSmoObject(urnParameters[i]) as Alert;
|
||||
|
||||
// check that the urn really points to an alert
|
||||
this.actions.AddAction(new EnableAlertAction(alert));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the actual enabling
|
||||
/// </summary>
|
||||
internal class EnableAlertAction : IProgressItem
|
||||
{
|
||||
private Alert alert;
|
||||
|
||||
public EnableAlertAction(Alert alert)
|
||||
{
|
||||
if (alert == null)
|
||||
{
|
||||
throw new ArgumentNullException("alert");
|
||||
}
|
||||
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a user friendly description of this task.Used in the description
|
||||
/// of the progress dialog.
|
||||
/// </summary>
|
||||
/// <returns>Description of the aler</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.alert == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.EnableAlertDescription(this.alert.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Enable the alert
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.EnablingAlert(this.alert.Name)");
|
||||
|
||||
this.alert.IsEnabled = true;
|
||||
this.alert.Alter();
|
||||
|
||||
// done
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.EnabledAlert(this.alert.Name)");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disable Alerts
|
||||
|
||||
/// <summary>
|
||||
/// Disable one or more alerts
|
||||
/// </summary>
|
||||
internal class DisableAgentAlerts : AgentAction
|
||||
{
|
||||
public DisableAgentAlerts(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Alert alert = this.smoServer.GetSmoObject(urnParameters[i]) as Alert;
|
||||
|
||||
// check that the urn really points to an alert
|
||||
this.actions.AddAction(new DisableAlertAction(alert));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actually disable the alert
|
||||
/// </summary>
|
||||
internal class DisableAlertAction : IProgressItem
|
||||
{
|
||||
private Alert alert;
|
||||
|
||||
public DisableAlertAction(Alert alert)
|
||||
{
|
||||
if (alert == null)
|
||||
{
|
||||
throw new ArgumentNullException("alert");
|
||||
}
|
||||
|
||||
this.alert = alert;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.alert == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.DisableAlertDescription(this.alert.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the alert
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.DisablingAlert(this.alert.Name)");
|
||||
|
||||
this.alert.IsEnabled = false;
|
||||
this.alert.Alter();
|
||||
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.DisabledAlert(this.alert.Name)");
|
||||
|
||||
/// done
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JobAction
|
||||
|
||||
internal class JobAction : AgentAction
|
||||
{
|
||||
public JobAction(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize context for actions on Jobs
|
||||
/// Job Activity monitor can call with list if jobids.
|
||||
/// All other existing callers may call with list of urns
|
||||
/// To support above 2 scenarios, this method in base class initializes urnParameters if it was not initialized by
|
||||
/// AgentAction class's constructor
|
||||
/// </summary>
|
||||
protected void InitializeContext()
|
||||
{
|
||||
// If Urn parameters were not initialized it is possible that
|
||||
// jobids were passed in by caller instead of list of urns
|
||||
if (null == urnParameters || urnParameters.Length == 0)
|
||||
{
|
||||
StringCollection jobIdStrings = new StringCollection();
|
||||
|
||||
// get list of job ids that were passed in
|
||||
param.GetParam("jobid", jobIdStrings);
|
||||
|
||||
urnParameters = new Urn[jobIdStrings.Count];
|
||||
int index = 0;
|
||||
if (jobIdStrings.Count > 0)
|
||||
{
|
||||
foreach (string jobIdString in jobIdStrings)
|
||||
{
|
||||
Job job = smoServer.JobServer.Jobs.ItemById(Guid.Parse(jobIdString));
|
||||
urnParameters[index++] = new Urn(job.Urn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enable Jobs
|
||||
|
||||
internal class EnableAgentJobs : JobAction
|
||||
{
|
||||
public EnableAgentJobs(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
InitializeContext();
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Job job = this.smoServer.GetSmoObject(urnParameters[i]) as Job;
|
||||
|
||||
// check that the urn really points to a Job
|
||||
this.actions.AddAction(new EnableJobAction(job));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// class that actually enables the job
|
||||
internal class EnableJobAction : IProgressItem
|
||||
{
|
||||
private Job job;
|
||||
|
||||
public EnableJobAction(Job job)
|
||||
{
|
||||
if (job == null)
|
||||
{
|
||||
throw new ArgumentNullException("job");
|
||||
}
|
||||
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate user friendly description of the action. This is displayed in the
|
||||
/// progress dialog.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.job == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.EnableJobDescription(this.job.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable the Job
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.EnablingJob(this.job.Name)");
|
||||
|
||||
this.job.IsEnabled = true;
|
||||
this.job.Alter();
|
||||
|
||||
|
||||
// done
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.EnabledJob(this.job.Name)");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Disable Jobs
|
||||
|
||||
/// <summary>
|
||||
/// Disable a job
|
||||
/// </summary>
|
||||
internal class DisableAgentJobs : JobAction
|
||||
{
|
||||
public DisableAgentJobs(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
InitializeContext();
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Job job = this.smoServer.GetSmoObject(urnParameters[i]) as Job;
|
||||
|
||||
// check that the urn really points to an job
|
||||
this.actions.AddAction(new DisableJobAction(job));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class DisableJobAction : IProgressItem
|
||||
{
|
||||
private Job job;
|
||||
|
||||
public DisableJobAction(Job job)
|
||||
{
|
||||
if (job == null)
|
||||
{
|
||||
throw new ArgumentNullException("job");
|
||||
}
|
||||
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.job == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.DisableJobDescription(this.job.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the Job
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.DisablingJob(this.job.Name)");
|
||||
|
||||
this.job.IsEnabled = false;
|
||||
this.job.Alter();
|
||||
|
||||
// done
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.DisabledJob(this.job.Name)");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Start Job
|
||||
|
||||
/// <summary>
|
||||
/// Start an agent job. If the jobs have multiple steps we will show a dialog that asks
|
||||
/// which step the job should be started on.
|
||||
/// </summary>
|
||||
internal class StartAgentJobs : JobAction
|
||||
{
|
||||
public StartAgentJobs(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
this.actions.CloseOnUserCancel = true;
|
||||
this.actions.QuitOnError = true;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method is generates list of actions and it is gets called from the OnLaod of base Form method
|
||||
/// </summary>
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
InitializeContext();
|
||||
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Job job = this.smoServer.GetSmoObject(urnParameters[i]) as Job;
|
||||
|
||||
|
||||
string selectedStep = null;
|
||||
|
||||
DataTable dtSteps = GetJobDataSteps(job);
|
||||
if (dtSteps == null || dtSteps.Rows == null)
|
||||
continue;
|
||||
|
||||
if (dtSteps.Rows.Count > 1) //check if job is multi step job
|
||||
{
|
||||
// selectedStep = ShowStepDialog(job, dtSteps);
|
||||
if (selectedStep == null) //check if the job was canceled
|
||||
continue;
|
||||
}
|
||||
|
||||
//Copy the LastRunTime of the job into prevRunTime before the job started.
|
||||
DateTime prevRunTime = job.LastRunDate;
|
||||
this.actions.AddAction(new StartJobAction(job, selectedStep));
|
||||
this.actions.AddAction(new WaitForJobToFinishAction(job, prevRunTime));
|
||||
}
|
||||
|
||||
if (this.actions.Count <= 0)
|
||||
{
|
||||
//This is a workaround for the class dialog invocation problem
|
||||
//This dialog is invoked from two places
|
||||
//JobsActivityMonitor (JobsPanel.cs) OnStartJobAtStep method
|
||||
// and SSMS Context menu mpu\ssms\shared\SqlMgmt\src\RunningFormsTable.cs method StartFormExecution
|
||||
//It is no way for us to prevent the dialog execution from the context menu (when job was canceled), besides redisgning the architecture
|
||||
//this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns list of steps of the given job
|
||||
/// </summary>
|
||||
/// <param name="job"></param>
|
||||
/// <returns>returns list of steps</returns>
|
||||
private DataTable GetJobDataSteps(Job job)
|
||||
{
|
||||
if (job == null || job.Parent == null || job.Parent.Parent == null)
|
||||
return null;
|
||||
|
||||
// perform an enumerator query to get the steps. We could use the
|
||||
// SMO step object but this is too inefficient as it generates a batch
|
||||
// per step.
|
||||
Request request = new Request();
|
||||
|
||||
request.Fields = new string[] {"Name", "ID", "SubSystem"};
|
||||
request.Urn = job.Urn + "/Step";
|
||||
request.OrderByList = new OrderBy[] {new OrderBy("ID", OrderBy.Direction.Asc)};
|
||||
|
||||
Enumerator en = new Enumerator();
|
||||
return en.Process(job.Parent.Parent.ConnectionContext, request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class implements the feature described in project tracking bug 37519.
|
||||
/// The point is to poll the server for the status of the job to give the user
|
||||
/// some indication of whether the job succeeded or failed. Polls every 3 seconds.
|
||||
/// </summary>
|
||||
internal class WaitForJobToFinishAction : IProgressItem
|
||||
{
|
||||
private Job job;
|
||||
private DateTime prevRunTime;
|
||||
private ManualResetEvent abortEvent;
|
||||
private const int ServerPollingInterval = 3000;
|
||||
|
||||
public WaitForJobToFinishAction(Job job, DateTime prevRunTime)
|
||||
{
|
||||
this.job = job;
|
||||
this.prevRunTime = prevRunTime;
|
||||
this.abortEvent = new ManualResetEvent(false); //initial set to busy
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevent default constructor
|
||||
/// </summary>
|
||||
private WaitForJobToFinishAction()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// generates a friendly description of this step. Used by the progress dialog
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.job == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.ExecuteJob(job.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method triggers abort event for the action thread
|
||||
/// </summary>
|
||||
public void Abort()
|
||||
{
|
||||
this.abortEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the action for this class
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">array index of this particular action</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
ProgressStatus status = ProgressStatus.Error;
|
||||
|
||||
bool jobFinished = false;
|
||||
|
||||
JobServer jobServer = job.Parent;
|
||||
JobCategory category = jobServer.JobCategories[job.Category];
|
||||
|
||||
if (category.CategoryType == CategoryType.MultiServerJob)
|
||||
{
|
||||
actions.Progress.UpdateActionDescription(index, "AgentActionSR.RequestPostedToTargetServers");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
|
||||
status = ProgressStatus.Aborted;
|
||||
|
||||
// now wait for job to finish...
|
||||
while (!this.abortEvent.WaitOne(WaitForJobToFinishAction.ServerPollingInterval))
|
||||
{
|
||||
if (actions.Progress.IsAborted)
|
||||
break;
|
||||
|
||||
this.job.Refresh();
|
||||
// If this job hasn't started yet then don't check for its status
|
||||
if (this.prevRunTime != job.LastRunDate)
|
||||
{
|
||||
switch (this.job.CurrentRunStatus)
|
||||
{
|
||||
case JobExecutionStatus.Idle:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, 100);
|
||||
|
||||
// see if the job succeeded.
|
||||
if (this.job.LastRunOutcome == CompletionResult.Failed)
|
||||
{
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Error);
|
||||
actions.Progress.AddActionException(index,
|
||||
new Exception("AgentActionSR.JobFailed(job.Name)"));
|
||||
status = ProgressStatus.Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.Progress.UpdateActionDescription(index, "AgentActionSR.ExecuteJob(job.Name)");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
status = ProgressStatus.Success;
|
||||
}
|
||||
|
||||
jobFinished = true;
|
||||
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.Suspended:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.Suspended");
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.BetweenRetries:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.BetweenRetries");
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.Executing:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.Executing");
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.PerformingCompletionAction:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.PerformingCompletionAction");
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.WaitingForStepToFinish:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.WaitingForStepToFinish");
|
||||
break;
|
||||
|
||||
case JobExecutionStatus.WaitingForWorkerThread:
|
||||
|
||||
actions.Progress.UpdateActionProgress(index, "AgentActionSR.WaitingForWorkerThread");
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown JobExecutionStatus, keep waiting.
|
||||
System.Diagnostics.Debug.Assert(false,
|
||||
"Unknown JobExecutionStatus found while waiting for job execution to finish");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (jobFinished)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// starts a job
|
||||
/// </summary>
|
||||
internal class StartJobAction : IProgressItem
|
||||
{
|
||||
private Job job;
|
||||
//Represent selected job step if any
|
||||
private string currentJobStep;
|
||||
|
||||
public StartJobAction(Job job, string jobStep)
|
||||
{
|
||||
// need a job. The delegate can be null
|
||||
if (job == null)
|
||||
{
|
||||
throw new ArgumentNullException("job");
|
||||
}
|
||||
|
||||
this.job = job;
|
||||
this.currentJobStep = jobStep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// generates a friendly description of this step. Used by the progress dialog
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.job == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.StartJobDescription(this.job.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start the Job
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
ProgressStatus status = ProgressStatus.Success;
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
|
||||
// perform an enumerator query to get the steps. We could use the
|
||||
// SMO step object but this is too inefficient as it generates a batch
|
||||
// per step.
|
||||
Request request = new Request();
|
||||
|
||||
request.Fields = new string[] {"Name", "ID", "SubSystem"};
|
||||
request.Urn = this.job.Urn + "/Step";
|
||||
request.OrderByList = new OrderBy[] {new OrderBy("ID", OrderBy.Direction.Asc)};
|
||||
|
||||
if (this.currentJobStep != null)
|
||||
{
|
||||
actions.Progress.AddActionInfoString(index,
|
||||
"AgentActionSR.StartJobWithStep(this.job.Name, this.currentJobStep)");
|
||||
this.job.Start(this.currentJobStep);
|
||||
}
|
||||
else
|
||||
{
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.StartingJob(this.job.Name)");
|
||||
this.job.Start();
|
||||
}
|
||||
|
||||
// done
|
||||
actions.Progress.UpdateActionStatus(index, status);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Stop Job
|
||||
|
||||
/// <summary>
|
||||
/// stop a job
|
||||
/// </summary>
|
||||
internal class StopAgentJobs : JobAction
|
||||
{
|
||||
public StopAgentJobs(XmlDocument document, IServiceProvider source)
|
||||
: base(document, source)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void GenerateActions()
|
||||
{
|
||||
if (this.smoServer != null)
|
||||
{
|
||||
InitializeContext();
|
||||
|
||||
for (int i = 0; i < this.urnParameters.Length; i++)
|
||||
{
|
||||
Job job = this.smoServer.GetSmoObject(urnParameters[i]) as Job;
|
||||
|
||||
// check that the urn really points to an job
|
||||
this.actions.AddAction(new StopJobAction(job));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// class that actually stops a running job
|
||||
/// </summary>
|
||||
internal class StopJobAction : IProgressItem
|
||||
{
|
||||
private Job job;
|
||||
|
||||
public StopJobAction(Job job)
|
||||
{
|
||||
if (job == null)
|
||||
{
|
||||
throw new ArgumentNullException("job");
|
||||
}
|
||||
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a user friendly description of this task. Used in the description
|
||||
/// of the progress dialog.
|
||||
/// </summary>
|
||||
/// <returns>Description of the action</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.job == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return "AgentActionSR.StopJobDescription(this.job.Name)";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop the Job
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">this actions index into the actions collection</param>
|
||||
/// <returns></returns>
|
||||
public ProgressStatus DoAction(ProgressItemCollection actions, int index)
|
||||
{
|
||||
//parameter check
|
||||
if (actions == null)
|
||||
{
|
||||
throw new ArgumentNullException("actions");
|
||||
}
|
||||
|
||||
// in progress
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.InProgress);
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.StoppingJob(this.job.Name)");
|
||||
job.Stop();
|
||||
|
||||
// done
|
||||
actions.Progress.AddActionInfoString(index, "AgentActionSR.StoppedJob(this.job.Name)");
|
||||
actions.Progress.UpdateActionStatus(index, ProgressStatus.Success);
|
||||
return ProgressStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
interface IFilterDefinition
|
||||
{
|
||||
object ShallowClone();
|
||||
|
||||
void ShallowCopy(object template);
|
||||
|
||||
void ResetToDefault();
|
||||
|
||||
bool IsDefault();
|
||||
|
||||
bool Enabled { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// Execution mode enumeration Success if execution succeeded of Failure otherwise for now.
|
||||
/// This enumeration might be refined more as there are needs for it
|
||||
/// </summary>
|
||||
public enum ExecutionMode
|
||||
{
|
||||
/// <summary>
|
||||
/// indicates that the operation failed
|
||||
/// </summary>
|
||||
Failure = 0,
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the operation succeded
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// indicates that the operation was canceled
|
||||
/// </summary>
|
||||
Cancel
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
public class PreProcessExecutionInfo
|
||||
{
|
||||
private RunType runType;
|
||||
private string script;
|
||||
private PreProcessExecutionInfo() {}
|
||||
|
||||
internal PreProcessExecutionInfo(RunType runType)
|
||||
{
|
||||
this.runType = runType;
|
||||
}
|
||||
|
||||
public RunType RunType
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.runType;
|
||||
}
|
||||
}
|
||||
|
||||
public string Script
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.script;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.script = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// IExecutionAwareSqlControlCollection allows control's container to do pre and post
|
||||
/// processing of the execution commands
|
||||
/// </summary>
|
||||
public interface IExecutionAwareSqlControlCollection : ISqlControlCollection
|
||||
{
|
||||
/// <summary>
|
||||
/// called before dialog's host executes actions on all panels in the dialog one by one.
|
||||
/// If something fails inside this function and the execution should be aborted,
|
||||
/// it can either raise an exception [in which case the framework will show message box with exception text]
|
||||
/// or set executionResult out parameter to be ExecutionMode.Failure
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
/// <param name="executionInfo">information about execution action</param>
|
||||
/// <param name="executionResult">result of the execution</param>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false if everything
|
||||
/// has been done by this function
|
||||
/// NOTE: in case of returning false during scripting operation
|
||||
/// PreProcessExecutionInfo.Script property of executionInfo parameter
|
||||
/// MUST be set by this function [if execution result is success]
|
||||
/// </returns>
|
||||
bool PreProcessExecution(PreProcessExecutionInfo executionInfo, out ExecutionMode executionResult);
|
||||
|
||||
/// <summary>
|
||||
/// called when the host received Cancel request. NOTE: this method can return while
|
||||
/// operation is still being canceled
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the host should do standard cancel for the currently running view or
|
||||
/// false if the Cancel operation was done entirely inside this method and there is nothing
|
||||
/// extra that should be done
|
||||
/// </returns>
|
||||
bool Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// called after dialog's host executes actions on all panels in the dialog one by one
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
/// <param name="executionMode">result of the execution</param>
|
||||
/// <param name="runType">type of execution</param>
|
||||
void PostProcessExecution(RunType runType, ExecutionMode executionMode);
|
||||
|
||||
/// <summary>
|
||||
/// called before dialog's host executes OnReset method on all panels in the dialog one by one
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false if everything
|
||||
/// has been done by this function
|
||||
/// </returns>
|
||||
bool PreProcessReset();
|
||||
|
||||
/// <summary>
|
||||
/// called after dialog's host executes OnReset method on all panels in the dialog one by one
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
void PostProcessReset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
#region interfaces
|
||||
/// <summary>
|
||||
/// Interface that supports the delegation of individual actions in the progress dialog
|
||||
/// to individual classes.
|
||||
/// </summary>
|
||||
public interface IProgressItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Perform the action for this class
|
||||
/// </summary>
|
||||
/// <param name="actions">Actions collection</param>
|
||||
/// <param name="index">array index of this particular action</param>
|
||||
/// <returns></returns>
|
||||
ProgressStatus DoAction(ProgressItemCollection actions, int index);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// defines notion of sitable object
|
||||
/// </summary>
|
||||
public interface IObjectWithSite
|
||||
{
|
||||
void SetSite(System.IServiceProvider sp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ISqlControlCollection allows access to a collection of dialog views
|
||||
/// </summary>
|
||||
public interface ISqlControlCollection : IObjectWithSite
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
// these map to the values for @execution_status
|
||||
// that can be passed to sp_help_job (except the first one!)
|
||||
// also the same as the smo enum JobExecutionStatus.
|
||||
internal enum EnumStatus
|
||||
{
|
||||
All = -1,
|
||||
NotIdleOrSuspended = 0,
|
||||
Executing = 1,
|
||||
WaitingForWorkerThread = 2,
|
||||
BetweenRetries = 3,
|
||||
Idle = 4,
|
||||
Suspended = 5,
|
||||
WaitingForStepToFinish = 6,
|
||||
PerformingCompletionAction = 7
|
||||
}
|
||||
|
||||
//
|
||||
// these values map to CompletionResult values, except the first.
|
||||
//
|
||||
internal enum EnumCompletionResult
|
||||
{
|
||||
All = -1,
|
||||
Failed = 0,
|
||||
Succeeded = 1,
|
||||
Retry = 2,
|
||||
Cancelled = 3,
|
||||
InProgress = 4,
|
||||
Unknown = 5
|
||||
}
|
||||
|
||||
//
|
||||
// for boolean job properties
|
||||
//
|
||||
internal enum EnumThreeState
|
||||
{
|
||||
All,
|
||||
Yes,
|
||||
No
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JobsFilter class - used to allow user to set filtering options for All Jobs Panel
|
||||
/// </summary>
|
||||
internal class JobActivityFilter : IFilterDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
public JobActivityFilter()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
#region Properties
|
||||
|
||||
private DateTime lastRunDate = new DateTime();
|
||||
private DateTime nextRunDate = new DateTime();
|
||||
private string name = string.Empty;
|
||||
private string category = string.Empty;
|
||||
private EnumStatus status = EnumStatus.All;
|
||||
private EnumThreeState enabled = EnumThreeState.All;
|
||||
private EnumThreeState runnable = EnumThreeState.All;
|
||||
private EnumThreeState scheduled = EnumThreeState.All;
|
||||
private EnumCompletionResult lastRunOutcome = EnumCompletionResult.All;
|
||||
|
||||
private bool filterdefinitionEnabled = false;
|
||||
|
||||
public EnumCompletionResult LastRunOutcome
|
||||
{
|
||||
get
|
||||
{
|
||||
return lastRunOutcome;
|
||||
}
|
||||
set
|
||||
{
|
||||
lastRunOutcome = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return name;
|
||||
}
|
||||
set
|
||||
{
|
||||
name = value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public EnumThreeState Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public EnumStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
return status;
|
||||
}
|
||||
set
|
||||
{
|
||||
status = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime LastRunDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return lastRunDate;
|
||||
}
|
||||
set
|
||||
{
|
||||
lastRunDate = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime NextRunDate
|
||||
{
|
||||
get
|
||||
{
|
||||
return nextRunDate;
|
||||
}
|
||||
set
|
||||
{
|
||||
nextRunDate = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Category
|
||||
{
|
||||
get
|
||||
{
|
||||
return category;
|
||||
}
|
||||
set
|
||||
{
|
||||
category = value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
public EnumThreeState Runnable
|
||||
{
|
||||
get
|
||||
{
|
||||
return runnable;
|
||||
}
|
||||
set
|
||||
{
|
||||
runnable = value;
|
||||
}
|
||||
}
|
||||
|
||||
public EnumThreeState Scheduled
|
||||
{
|
||||
get
|
||||
{
|
||||
return scheduled;
|
||||
}
|
||||
set
|
||||
{
|
||||
scheduled = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IFilterDefinition - interface implementation
|
||||
/// <summary>
|
||||
/// resets values of this object to default contraint values
|
||||
/// </summary>
|
||||
void IFilterDefinition.ResetToDefault()
|
||||
{
|
||||
lastRunDate = new DateTime();
|
||||
nextRunDate = new DateTime();
|
||||
name = string.Empty;
|
||||
category = string.Empty;
|
||||
enabled = EnumThreeState.All;
|
||||
status = EnumStatus.All;
|
||||
runnable = EnumThreeState.All;
|
||||
scheduled = EnumThreeState.All;
|
||||
lastRunOutcome = EnumCompletionResult.All;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks if the filter is the same with the default filter
|
||||
/// </summary>
|
||||
bool IFilterDefinition.IsDefault()
|
||||
{
|
||||
return (lastRunDate.Ticks == 0 &&
|
||||
nextRunDate.Ticks == 0 &&
|
||||
name.Length == 0 &&
|
||||
category.Length == 0 &&
|
||||
enabled == EnumThreeState.All &&
|
||||
status == EnumStatus.All &&
|
||||
runnable == EnumThreeState.All &&
|
||||
scheduled == EnumThreeState.All &&
|
||||
lastRunOutcome == EnumCompletionResult.All);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// creates a shallow clone
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object IFilterDefinition.ShallowClone()
|
||||
{
|
||||
JobActivityFilter clone = new JobActivityFilter();
|
||||
|
||||
clone.LastRunDate = this.LastRunDate;
|
||||
clone.NextRunDate = this.NextRunDate;
|
||||
clone.Name = this.Name;
|
||||
clone.Category = this.Category;
|
||||
clone.Enabled = this.Enabled;
|
||||
clone.Status = this.Status;
|
||||
clone.Runnable = this.Runnable;
|
||||
clone.Scheduled = this.Scheduled;
|
||||
clone.LastRunOutcome = this.LastRunOutcome;
|
||||
|
||||
(clone as IFilterDefinition).Enabled = (this as IFilterDefinition).Enabled;
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// setup-s filter definition based on a template
|
||||
/// </summary>
|
||||
/// <param name="template"></param>
|
||||
void IFilterDefinition.ShallowCopy(object template)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(template is JobActivityFilter);
|
||||
|
||||
JobActivityFilter f = template as JobActivityFilter;
|
||||
|
||||
this.LastRunDate = f.LastRunDate;
|
||||
this.NextRunDate = f.NextRunDate;
|
||||
this.Name = f.Name;
|
||||
this.Category = f.Category;
|
||||
this.Enabled = f.Enabled;
|
||||
this.Status = f.Status;
|
||||
this.Runnable = f.Runnable;
|
||||
this.Scheduled = f.Scheduled;
|
||||
this.LastRunOutcome = f.LastRunOutcome;
|
||||
|
||||
(this as IFilterDefinition).Enabled = (template as IFilterDefinition).Enabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// tells us if filtering is enabled or diabled
|
||||
/// a disabled filter lets everything pass and filters nothing out
|
||||
/// </summary>
|
||||
bool IFilterDefinition.Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return filterdefinitionEnabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
filterdefinitionEnabled = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Build filter
|
||||
|
||||
private void AddPrefix(StringBuilder sb, bool clauseAdded)
|
||||
{
|
||||
if (clauseAdded)
|
||||
{
|
||||
sb.Append(" and ( ");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" ( ");
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSuffix(StringBuilder sb)
|
||||
{
|
||||
sb.Append(" ) ");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// fetch an xpath clause used for filtering
|
||||
/// jobs fetched by the enumerator.
|
||||
/// note that all other properties must be filtered on the client
|
||||
/// because enumerator will not filter properties that are fetched
|
||||
/// at post-process time. We can't even filter on the job name here
|
||||
/// since we have to do a case-insensitive "contains" comparision on the name.
|
||||
/// </summary>
|
||||
public string GetXPathClause()
|
||||
{
|
||||
if (this.enabled == EnumThreeState.All)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
bool clauseAdded = false;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("[");
|
||||
|
||||
//
|
||||
// enabled clause
|
||||
//
|
||||
if (this.enabled != EnumThreeState.All)
|
||||
{
|
||||
AddPrefix(sb, clauseAdded);
|
||||
sb.Append("@IsEnabled = " + (this.enabled == EnumThreeState.Yes ? "true() " : "false() "));
|
||||
AddSuffix(sb);
|
||||
clauseAdded = true;
|
||||
}
|
||||
|
||||
sb.Append("]");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,704 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
|
||||
#region LogSourceAggregation - ILogSource info built from multiple other sources
|
||||
internal class LogSourceAggregation : ILogSource, ITypedColumns, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const int cMaximumNotificationChunkSize = 128; // 16384 high no: faster aggregation, low no: responsive ui
|
||||
#endregion
|
||||
|
||||
#region Variables
|
||||
private string m_logName = null;
|
||||
private bool m_logInitialized = false;
|
||||
private string[] m_fieldNames = null;
|
||||
private TypedColumnCollection m_columnTypes = null;
|
||||
|
||||
List<ILogSource> m_originalSources = null;
|
||||
List<ILogSource> m_sources = null;
|
||||
ILogConstraints m_filter = null;
|
||||
private LogAggregator m_owner = null;
|
||||
private ILogEntry m_currentEntry = null;
|
||||
private List<ILogEntry> m_currentEntrySources = null;
|
||||
private List<Exception> m_exceptionList = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reverse order Property
|
||||
private bool ReverseOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="sources"></param>
|
||||
/// <param name="filter">if null no filter, else use it to filter every ILogEntry</param>
|
||||
public LogSourceAggregation (LogAggregator owner, string name, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
m_owner = owner;
|
||||
|
||||
m_logName = name;
|
||||
m_originalSources = new List<ILogSource>(sources);
|
||||
|
||||
m_fieldNames = AggregateFieldNames(sources);
|
||||
|
||||
AggregateColumnTypes(sources);
|
||||
|
||||
// if (filterTemplate != null)
|
||||
// {
|
||||
// m_filter = new LogConstraints(this, filterTemplate as LogConstraints);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
m_filter = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < m_originalSources.Count; ++i)
|
||||
{
|
||||
if (m_originalSources[i] is IDisposable)
|
||||
{
|
||||
(m_originalSources[i] as IDisposable).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
m_currentEntry = null;
|
||||
m_sources = null;
|
||||
m_exceptionList = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ILogSource interface implementation
|
||||
|
||||
bool ILogSource.OrderedByDateDescending
|
||||
{
|
||||
get {return this.ReverseOrder;}
|
||||
}
|
||||
|
||||
ILogEntry ILogSource.CurrentEntry
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_currentEntry;
|
||||
}
|
||||
}
|
||||
|
||||
bool ILogSource.ReadEntry()
|
||||
{
|
||||
//the m_currentEntrySources list contains the list of currentEntries for each logSource
|
||||
//when the readentry is called for the first time the list is null so we need to initialize it
|
||||
if (m_currentEntrySources == null)
|
||||
{
|
||||
m_sources = new List<ILogSource>(m_originalSources);
|
||||
m_currentEntrySources = new List<ILogEntry>(m_sources.Count);
|
||||
for (int i = 0; i < m_sources.Count; i++)
|
||||
{
|
||||
//the null value acts as a guard that indicates whether we have read all the entries from the current source
|
||||
//or if an error happened. That is why I initialize all the sources with null
|
||||
m_currentEntrySources.Add(null);
|
||||
try
|
||||
{
|
||||
|
||||
if (m_sources[i].CurrentEntry != null || (m_sources[i].ReadEntry()))
|
||||
{
|
||||
m_currentEntrySources[i] = m_sources[i].CurrentEntry;
|
||||
if (m_filter != null)
|
||||
{
|
||||
while (!m_filter.MatchLogEntry(m_sources[i].CurrentEntry))
|
||||
{
|
||||
//check if cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sources[i].ReadEntry())
|
||||
{
|
||||
m_currentEntrySources[i] = m_sources[i].CurrentEntry;
|
||||
|
||||
if (m_filter != null && !m_filter.IsEntryWithinFilterTimeWindow(m_currentEntrySources[i]))
|
||||
{
|
||||
m_currentEntrySources[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentEntrySources[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, m_sources[i].Name);
|
||||
m_currentEntrySources[i] = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//we check the currentEntrySources again to see if there are any entries read from the source
|
||||
//if not it means that either the source has no entries (that satisfy the filter if a filter is defined)
|
||||
//or an error happened so we need to close the reader and remove the source.
|
||||
for (int i = 0; i < m_currentEntrySources.Count; i++)
|
||||
{
|
||||
if (m_currentEntrySources[i] == null)
|
||||
{
|
||||
m_sources[i].CloseReader();
|
||||
m_sources.RemoveAt(i);
|
||||
m_currentEntrySources.RemoveAt(i);
|
||||
i--; //we need this to make the indexer point at the previous log source
|
||||
}
|
||||
}
|
||||
|
||||
int sourceindex = -1;
|
||||
|
||||
if (m_sources.Count == 1 && m_currentEntrySources[0] != null)
|
||||
{
|
||||
sourceindex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime maxtime = DateTime.MinValue;
|
||||
for (int i = 0; i < m_sources.Count; i++)
|
||||
{
|
||||
if (maxtime.CompareTo(m_currentEntrySources[i].PointInTime) <= 0)
|
||||
{
|
||||
maxtime = m_currentEntrySources[i].PointInTime;
|
||||
sourceindex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceindex > -1)
|
||||
{
|
||||
m_currentEntry = m_sources[sourceindex].CurrentEntry;
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
//check if cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sources[sourceindex].ReadEntry())
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = m_sources[sourceindex].CurrentEntry;
|
||||
|
||||
if (m_filter != null && !m_filter.IsEntryWithinFilterTimeWindow(m_currentEntrySources[sourceindex]))
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
while (m_filter != null && !m_filter.MatchLogEntry(m_sources[sourceindex].CurrentEntry));
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, m_sources[sourceindex].Name);
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ILogSource.CloseReader()
|
||||
{
|
||||
foreach (ILogSource source in m_originalSources)
|
||||
{
|
||||
source.CloseReader();
|
||||
}
|
||||
|
||||
m_currentEntrySources = null;
|
||||
m_currentEntry = null;
|
||||
m_exceptionList = null;
|
||||
|
||||
}
|
||||
|
||||
string ILogSource.Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_logName;
|
||||
}
|
||||
}
|
||||
|
||||
void ILogSource.Initialize()
|
||||
{
|
||||
if (m_logInitialized == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize original sources
|
||||
int n = m_originalSources.Count;
|
||||
int i = 0;
|
||||
for (i = 0; i < m_originalSources.Count; i++)
|
||||
{
|
||||
|
||||
ILogSource s = m_originalSources[i];
|
||||
|
||||
try
|
||||
{
|
||||
// format notification message
|
||||
m_owner.Raise_AggregationProgress("Raise_AggregationProgress", //LogViewerSR.AggregationProgress_Initialize(i + 1, n, (s.Name != null) ? s.Name.Trim() : String.Empty),
|
||||
0,
|
||||
null);
|
||||
|
||||
// initialize (load) inner source
|
||||
s.Initialize();
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, s.Name);
|
||||
m_originalSources.RemoveAt(i);
|
||||
s.CloseReader();
|
||||
i--;
|
||||
}
|
||||
|
||||
// check for cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// report all inner source loaded
|
||||
m_owner.Raise_AggregationProgress("LogViewerSR.AggregationProgress_InitializationDone",
|
||||
LogAggregator.cProgressLoaded,
|
||||
null);
|
||||
|
||||
|
||||
m_logInitialized = true;
|
||||
}
|
||||
|
||||
string[] ILogSource.FieldNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_fieldNames;
|
||||
}
|
||||
}
|
||||
|
||||
TypedColumnCollection ITypedColumns.ColumnTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_columnTypes;
|
||||
}
|
||||
}
|
||||
|
||||
ILogSource[] ILogSource.SubSources
|
||||
{
|
||||
get { return null;}
|
||||
}
|
||||
|
||||
ILogSource ILogSource.GetRefreshedClone()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
/// <summary>
|
||||
/// computes the available fields for the aggregated log source
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
internal static string[] AggregateFieldNames(ILogSource[] sources)
|
||||
{
|
||||
List<string> ar = new List<string>();
|
||||
|
||||
foreach(ILogSource s in sources)
|
||||
{
|
||||
if ((s != null) && (s.FieldNames != null))
|
||||
{
|
||||
foreach(string fieldName in s.FieldNames)
|
||||
{
|
||||
if (ar.Contains(fieldName))
|
||||
{
|
||||
continue; // do not add it again
|
||||
}
|
||||
ar.Add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ar.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// computes the available column types for the aggregated log source
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
private void AggregateColumnTypes(ILogSource[] sources)
|
||||
{
|
||||
m_columnTypes = new TypedColumnCollection();
|
||||
|
||||
foreach (ILogSource s in sources)
|
||||
{
|
||||
if (s is ITypedColumns)
|
||||
{
|
||||
ITypedColumns cs = (ITypedColumns)s;
|
||||
if ((cs != null) && (cs.ColumnTypes != null))
|
||||
{
|
||||
foreach (string fieldName in s.FieldNames)
|
||||
{
|
||||
m_columnTypes.AddColumnType(fieldName, cs.ColumnTypes.GetColumnType(fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks to see if somebody decided to cancel or stop the operation
|
||||
/// </summary>
|
||||
private bool IsCanceled()
|
||||
{
|
||||
return m_owner.CancelInternal || m_owner.StopInternal;
|
||||
}
|
||||
|
||||
|
||||
public IList<Exception> ExceptionList
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_exceptionList;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearExceptionList()
|
||||
{
|
||||
m_exceptionList = null;
|
||||
}
|
||||
|
||||
private void AddExceptionToExceptionList(Exception e, string sourceName)
|
||||
{
|
||||
e.Source = sourceName;
|
||||
|
||||
if (m_exceptionList == null)
|
||||
{
|
||||
m_exceptionList = new List<Exception>();
|
||||
}
|
||||
m_exceptionList.Add(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Conditional("DEBUG")] validate correctness of a log source
|
||||
/// <summary>
|
||||
/// validate if entries are in correct order
|
||||
///
|
||||
/// costly operation so we compile this only if "DEBUG" is defined
|
||||
/// iterates through all the entries and if their datetime is different the
|
||||
/// DateTime.MinValue or DateTime.MaxValue compares it with adjacent entries
|
||||
///
|
||||
/// we do not compare subentries as aggregation is performed
|
||||
/// only at entries level (subentries are always linked to thier parent entry)
|
||||
///
|
||||
/// the order should be ascending (newer logs are after older logs) if reverseOrder = false
|
||||
/// the order should be descending (newer logs are before older logs) if reverseOrder = true
|
||||
/// </summary>
|
||||
/// <param name="entries"></param>
|
||||
/// <param name="reverseOrder"></param>
|
||||
[System.Diagnostics.Conditional("DEBUG")]
|
||||
private static void ConditionalDEBUG_ValidateLogEntriesOrder(List<ILogEntry> entries,
|
||||
bool reverseOrder)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("LogSourceAggregation.ConditionalDEBUG_ValidateLogEntriesOrder ------- reverseOrder=" + reverseOrder.ToString());
|
||||
|
||||
if ((entries == null) || (entries.Count < 2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (entries.Count - 1); ++i)
|
||||
{
|
||||
int j = i + 1;
|
||||
|
||||
DateTime dti = entries[i].PointInTime;
|
||||
DateTime dtj = entries[j].PointInTime;
|
||||
|
||||
if (
|
||||
(dti != DateTime.MinValue) && (dti != DateTime.MaxValue) &&
|
||||
(dtj != DateTime.MinValue) && (dtj != DateTime.MaxValue)
|
||||
)
|
||||
{
|
||||
// if logs are comming from same source then we dont Assert since it is not
|
||||
// the aggregator algoritm to blame but the log source provider who broke
|
||||
// the assumption that log sources are coming already pre-sorted
|
||||
if ((entries[i].OriginalSourceTypeName == entries[j].OriginalSourceTypeName) &&
|
||||
(entries[i].OriginalSourceName == entries[j].OriginalSourceName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region ITypedColumns Members
|
||||
|
||||
|
||||
public void HyperLinkClicked(string sourcename, string columnname, string hyperlink, long row, int column)
|
||||
{
|
||||
foreach (ILogSource s in m_originalSources)
|
||||
{
|
||||
if ((s is ITypedColumns) &&
|
||||
(s.Name == sourcename)) // The original source for the row containing the hyperlink
|
||||
{
|
||||
((ITypedColumns)s).HyperLinkClicked(sourcename, columnname, hyperlink, row, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TypedColumnCollection
|
||||
|
||||
internal class TypedColumnCollection
|
||||
{
|
||||
private Dictionary<string, int> m_typedColumns = null;
|
||||
|
||||
internal TypedColumnCollection()
|
||||
{
|
||||
m_typedColumns = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
internal void AddColumnType(string columnName, int columnType)
|
||||
{
|
||||
if (!m_typedColumns.ContainsKey(columnName))
|
||||
{
|
||||
m_typedColumns.Add(columnName, columnType);
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetColumnType(string columnName)
|
||||
{
|
||||
int returnType;
|
||||
if (m_typedColumns.TryGetValue(columnName, out returnType))
|
||||
{
|
||||
return returnType;
|
||||
}
|
||||
return GridColumnType.Text;
|
||||
}
|
||||
|
||||
internal bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_typedColumns.Count == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region LogAggregator class - ILogAggregator algorithm
|
||||
/// <summary>
|
||||
/// Summary description for LogAggregator.
|
||||
/// </summary>
|
||||
internal class LogAggregator : ILogAggregator
|
||||
{
|
||||
#region Constants
|
||||
internal const int cProgressLogCreated = 1;
|
||||
internal const int cProgressLoaded = 15;
|
||||
internal const int cProgressAlmostDone = 95;
|
||||
internal const int cProgressDone = 100;
|
||||
#endregion
|
||||
|
||||
#region Properties - CancelInternal (lock-ed access)
|
||||
private volatile bool m_boolCancelInternal = false;
|
||||
internal bool CancelInternal
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return m_boolCancelInternal;
|
||||
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
m_boolCancelInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private volatile bool m_boolStopInternal = false;
|
||||
internal bool StopInternal
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return m_boolStopInternal;
|
||||
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
m_boolStopInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool m_reverseOrder = true;
|
||||
internal bool ReverseOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_reverseOrder;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_reverseOrder = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Variables
|
||||
private LogSourceAggregation m_currentSource = null;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// create an log aggregator using a default empty cache
|
||||
/// </summary>
|
||||
public LogAggregator()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ILogAggregator interface implementation
|
||||
ILogSource ILogAggregator.PrepareAggregation(string outputLogSourceName, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
ILogSource outputSource = CreateUninitializedAggregation(outputLogSourceName, sources, filterTemplate);
|
||||
|
||||
m_currentSource = outputSource as LogSourceAggregation;
|
||||
|
||||
return outputSource;
|
||||
}
|
||||
|
||||
void ILogAggregator.CancelAsyncWork()
|
||||
{
|
||||
CancelInternal = true;
|
||||
}
|
||||
|
||||
void ILogAggregator.StopAsyncWork()
|
||||
{
|
||||
StopInternal = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CreateUninitializedAggregation algorithm
|
||||
/// <summary>
|
||||
/// agregates one or more sources -> creates a new (uninitialized) aggregation
|
||||
///
|
||||
/// NOTE:
|
||||
/// we also 'aggregate' only 1 source to gain the advantage offered by this algoritm
|
||||
/// of being able to pump entry-s to ui thread in chucks instead of sending all source
|
||||
/// in one shoot -> more responsive ui
|
||||
/// </summary>
|
||||
/// <param name="outputLogSourceName"></param>
|
||||
/// <param name="sources"></param>
|
||||
/// <param name="constraints">null if no filter</param>
|
||||
/// <returns></returns>
|
||||
private ILogSource CreateUninitializedAggregation(string outputLogSourceName, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
// zero sources - nothing we can do
|
||||
if ((sources == null) || (sources.Length==0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ILogSource newAggregation = null;
|
||||
try
|
||||
{
|
||||
// not in cache, so build it, add it to cache (if caching ok) and return it
|
||||
newAggregation = new LogSourceAggregation(this, outputLogSourceName, sources, filterTemplate);
|
||||
|
||||
return newAggregation;
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
Raise_AggregationProgress( "LogViewerSR.AggregationProgress_BeginInitialize",
|
||||
cProgressLogCreated,
|
||||
null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DelegateAggregationWorkImplementation - entry for - asynchronous invocation with callback ***** via delegate
|
||||
private List<Exception> m_exceptionsList = new List<Exception>();
|
||||
#endregion
|
||||
|
||||
#region Report Progress
|
||||
/// <summary>
|
||||
/// if job not null and callbackProgress available -> invoke progress delegate in ui thread
|
||||
/// </summary>
|
||||
/// <param name="job"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="percent"></param>
|
||||
internal void Raise_AggregationProgress(string message,
|
||||
int percent,
|
||||
IList<Exception> exceptionList)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using System;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using Microsoft.SqlServer.Management.Diagnostics;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
public class GridColumnType
|
||||
{
|
||||
public const int
|
||||
Text = 1,
|
||||
Button = 2,
|
||||
Bitmap = 3,
|
||||
Checkbox = 4,
|
||||
Hyperlink = 5,
|
||||
FirstCustomColumnType = 0x400;
|
||||
};
|
||||
|
||||
// /// <summary>
|
||||
/// ILogSourceTypeFactory knows to instantiate objects that deal with various types of logs
|
||||
/// -- e.g. a factory that knows how to handle SqlServer, SqlAgent and Windows NT logs
|
||||
/// </summary>
|
||||
internal interface ILogSourceTypeFactory
|
||||
{
|
||||
ILogSourceType[] SourceTypes {get;}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ILogSourceType describes the interface for log source types
|
||||
/// -- e.g. SqlServer, SqlAgent, Windows NT, file-stream, etc...
|
||||
/// </summary>
|
||||
internal interface ILogSourceType
|
||||
{
|
||||
string Name { get;}
|
||||
ILogSource[] Sources { get;}
|
||||
|
||||
void Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ILogSource describes a log source
|
||||
/// -- e.g. Current SqlServer log, Archive #3 of Sql Agent, NT Security log, etc...
|
||||
/// </summary>
|
||||
public interface ILogSource
|
||||
{
|
||||
string Name {get;}
|
||||
/// <summary>
|
||||
/// We allow only one initialization for the source. This is because upon aggregation we create a new LogSourceAggregation that
|
||||
/// contains the seperate sources and initialize it. So if a source is already initialized from previous collection we shouldn't
|
||||
/// initialize it again because we will have duplicate data.
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
ILogSource[] SubSources {get;}
|
||||
ILogEntry CurrentEntry { get;}
|
||||
string[] FieldNames {get;}
|
||||
ILogSource GetRefreshedClone();
|
||||
bool ReadEntry();
|
||||
void CloseReader();
|
||||
bool OrderedByDateDescending {get;}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ILogAggregator describes an algorithm that agregates multiple ILogSources
|
||||
/// -- e.g. algorithm that interleaves multiple logs sources based on log entry times
|
||||
/// </summary>
|
||||
internal interface ILogAggregator
|
||||
{
|
||||
ILogSource PrepareAggregation (string outputLogSourceName, ILogSource[] sources, ILogConstraints filter);
|
||||
void CancelAsyncWork(); //CancelAsyncWork stops the current aggregation and at the end closes all the open readers for the sources
|
||||
void StopAsyncWork(); // StopAsyncWork does the same thing as CancelAsyncWork but instead it leaves the readers open because this is used in incremental aggregation so we will resume the collection by adding the new source selected
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used for filtering and searching log entries
|
||||
/// </summary>
|
||||
internal interface ILogConstraints
|
||||
{
|
||||
bool MatchLogEntry(ILogEntry entry);
|
||||
bool IsEntryWithinFilterTimeWindow(ILogEntry entry);
|
||||
}
|
||||
|
||||
internal interface ITypedColumns
|
||||
{
|
||||
TypedColumnCollection ColumnTypes { get; }
|
||||
void HyperLinkClicked(string sourcename, string columnname, string hyperlink, long row, int column);
|
||||
}
|
||||
|
||||
#if false
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the storage view that creates a view of the data storage class
|
||||
/// </summary>
|
||||
internal interface ILogStorageView : IStorageView
|
||||
{
|
||||
ILogEntry EntryAt(long row);
|
||||
LogColumnInfo.RowInfo GetRowInfoAt(int row);
|
||||
ReadOnlyCollection<string> VisibleFieldNames { get; }
|
||||
void Clear();
|
||||
bool IsRowExpandable(int rowIndex);
|
||||
bool IsRowExpanded(int rowIndex);
|
||||
int RowLevel(int rowIndex);
|
||||
int ExpandRow(int rowIndex);
|
||||
int CollapseRow(int rowIndex);
|
||||
ReadOnlyCollection<string> VisibleSourceNames { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the Data storage class that can be either memory based or disk based.
|
||||
/// </summary>
|
||||
internal interface ILogDataStorage : IDataStorage, ITypedColumns
|
||||
{
|
||||
void Initialize();
|
||||
/// <summary>
|
||||
/// returns a view of the storage with the applied filter or a plain view if the filter is disabled.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ILogStorageView GetFilteredView();
|
||||
/// <summary>
|
||||
/// stops the storing of the data, This is used in the incremental aggregation so as to stop the collection without closing the readers
|
||||
/// </summary>
|
||||
void StopStoringData();
|
||||
/// <summary>
|
||||
/// cancels the collection and implies that the readers should be closed
|
||||
/// </summary>
|
||||
void CancelStoring();
|
||||
StorageNotifyDelegate StorageNotifyDelegate { get; set;}
|
||||
ReadOnlyCollection<string> FieldNames { get;}
|
||||
ILogSource AggregationSource { get; set;}
|
||||
|
||||
ReadOnlyCollection<string> VisibleFieldNames { get; set; }
|
||||
/// <summary>
|
||||
/// is used when no filter is applied in order to avoid going into aggregation mode and just output all available entries to the consumer
|
||||
/// </summary>
|
||||
bool NotifyAll { get; set; }
|
||||
bool IsCanceled { get;}
|
||||
/// <summary>
|
||||
/// is used to return a list with the row numbers that correspond to all the entries from the demanded sources
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IList<long> GetDemandedSourcesRowIndexList();
|
||||
/// <summary>
|
||||
/// This returns the fitler that was defined when we started the collection.
|
||||
/// This filter was pushed to the server
|
||||
/// </summary>
|
||||
LogConstraints CollectionFilter { get; set;}
|
||||
/// <summary>
|
||||
/// This returns the client side side filter that is currently set in order to
|
||||
/// show a filtered view of the collected data
|
||||
/// </summary>
|
||||
LogConstraints ClientFilter { get; set;}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the the sorting view which keeps a mapping of relative rows and absolute rows
|
||||
/// based on the column key
|
||||
/// </summary>
|
||||
internal interface ILogSortedView : IComparer<int>
|
||||
{
|
||||
void Initialize(ILogStorageView view);
|
||||
int KeyIndex { get; set; }
|
||||
bool IsDescending { get; set; }
|
||||
void SortData();
|
||||
int GetAbsoluteRowNumber(int iRelativeRow);
|
||||
void StopSortingData();
|
||||
int SortedRows();
|
||||
void ReverseSorting();
|
||||
void ExpandRow(int iRow, int subRowsCount);
|
||||
void CollapseRow(int iRow, int subRowsCount);
|
||||
void ExpandParentRows(ILogStorageView view);
|
||||
StorageNotifyDelegate StorageNotifyDelegate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// used by ui thread to schedule async invocations (ui should use AsyncCallback to get notified when compleated)
|
||||
/// </summary>
|
||||
internal delegate void DelegateAggregationWork(ILogDataStorage storage);
|
||||
|
||||
/// <summary>
|
||||
/// delegate used to notify that asyncronous aggregation job was completed
|
||||
/// -- e.g. an aggregator finished initializing some of its inner sources a ui component that displays progress of aggregation will be called
|
||||
/// </summary>
|
||||
internal delegate void DelegateAggregationProgress(object sender,
|
||||
string message,
|
||||
int percentage,
|
||||
IList<Exception> exceptionList);
|
||||
|
||||
/// <summary>
|
||||
/// event that is triggered when user changes visible columns or available columns
|
||||
/// -- e.g. user pop-ed up the ui beoyind a IColumnManager and wants to apply changes
|
||||
/// </summary>
|
||||
internal delegate void DelegateColumnsInformationChanged (object sender, string[] columns);
|
||||
|
||||
/// <summary>
|
||||
/// severity associated with a log entry (ILogEntry)
|
||||
// these should be ordered least severe to most severe where possible.
|
||||
/// </summary>
|
||||
public enum SeverityClass
|
||||
{
|
||||
Unknown = -1,
|
||||
Success,
|
||||
Information,
|
||||
SuccessAudit,
|
||||
InProgress,
|
||||
Retry,
|
||||
Warning,
|
||||
FailureAudit,
|
||||
Cancelled,
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// command that can be executed by a log viewer (ILogViewer)
|
||||
/// </summary>
|
||||
internal enum LogViewerCommand
|
||||
{
|
||||
Load = 0,
|
||||
Refresh,
|
||||
Export,
|
||||
Columns,
|
||||
Filter,
|
||||
Search,
|
||||
Delete,
|
||||
Help,
|
||||
Close,
|
||||
Cancel
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// command options
|
||||
/// </summary>
|
||||
internal enum LogViewerCommandOption
|
||||
{
|
||||
None = 0,
|
||||
Hide,
|
||||
Show
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for various events
|
||||
/// related to LogViewer
|
||||
/// </summary>
|
||||
internal class LogViewerEventArgs : EventArgs
|
||||
{
|
||||
private LogViewerCommand command;
|
||||
|
||||
public LogViewerEventArgs(LogViewerCommand command)
|
||||
{
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public LogViewerCommand Command
|
||||
{
|
||||
get { return command; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Xml;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Specialized;
|
||||
using Microsoft.SqlServer.Management.Smo;
|
||||
using Microsoft.SqlServer.Management.Common;
|
||||
using Microsoft.SqlServer.Management.Diagnostics;
|
||||
using Microsoft.SqlTools.ServiceLayer.Admin;
|
||||
using Microsoft.SqlTools.ServiceLayer.Agent;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Management
|
||||
{
|
||||
/// <summary>
|
||||
/// base class that can be used to derived from for the main classes [containers] of the dialogs
|
||||
/// </summary>
|
||||
public class ManagementActionBase : IDisposable
|
||||
{
|
||||
#region Members
|
||||
|
||||
/// <summary>
|
||||
/// selected node as specified to SelectNode method
|
||||
/// </summary>
|
||||
//private TreeNode selectedNode;
|
||||
|
||||
/// <summary>
|
||||
/// service provider of our host. We should direct all host-specific requests to the services
|
||||
/// implemented by this provider
|
||||
/// </summary>
|
||||
private IServiceProvider serviceProvider;
|
||||
|
||||
/// <summary>
|
||||
/// data container with initialization-related information
|
||||
/// </summary>
|
||||
private CDataContainer dataContainer;
|
||||
//whether we assume complete ownership over it.
|
||||
//We set this member once the dataContainer is set to be non-null
|
||||
private bool ownDataContainer = true;
|
||||
|
||||
//if derived class tries to call a protected method that relies on service provider,
|
||||
//and the service provider hasn't been set yet, we will cache the values and will
|
||||
//propagate them when we get the provider set
|
||||
//private System.Drawing.Icon cachedIcon = null;
|
||||
private string cachedCaption = null;
|
||||
|
||||
//SMO Server connection that MUST be used for all enumerator calls
|
||||
//We'll get this object out of CDataContainer, that must be initialized
|
||||
//property by the initialization code
|
||||
private ServerConnection serverConnection;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
public ManagementActionBase()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable implementation
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
//BUGBUG - do we need finalizer
|
||||
Dispose(true);//call protected virtual method
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// do the deterministic cleanup
|
||||
/// </summary>
|
||||
/// <param name="disposing"></param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
//dispose CDataContainer if needed
|
||||
if (this.dataContainer != null)
|
||||
{
|
||||
if (this.ownDataContainer)
|
||||
{
|
||||
this.dataContainer.Dispose();
|
||||
}
|
||||
this.dataContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IObjectWithSite implementation
|
||||
|
||||
public virtual void SetSite(IServiceProvider sp)
|
||||
{
|
||||
if (sp == null)
|
||||
{
|
||||
throw new ArgumentNullException("sp");
|
||||
}
|
||||
|
||||
//allow to be sited only once
|
||||
if (this.serviceProvider == null)
|
||||
{
|
||||
//cache the service provider
|
||||
this.serviceProvider = sp;
|
||||
|
||||
//call protected virtual method to enable derived classes to do initialization
|
||||
//OnHosted();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IExecutionAwareSqlControlCollection implementation
|
||||
|
||||
/// <summary>
|
||||
/// called before dialog's host executes actions on all panels in the dialog one by one.
|
||||
/// If something fails inside this function and the execution should be aborted,
|
||||
/// it can either raise an exception [in which case the framework will show message box with exception text]
|
||||
/// or set executionResult out parameter to be ExecutionMode.Failure
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
/// <param name="executionInfo">information about execution action</param>
|
||||
/// <param name="executionResult">result of the execution</param>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false if everything
|
||||
/// has been done by this function
|
||||
/// NOTE: in case of returning false during scripting operation
|
||||
/// PreProcessExecutionInfo.Script property of executionInfo parameter
|
||||
/// MUST be set by this function [if execution result is success]
|
||||
/// </returns>
|
||||
public bool PreProcessExecution(PreProcessExecutionInfo executionInfo, out ExecutionMode executionResult)
|
||||
{
|
||||
//we start from failure
|
||||
executionResult = ExecutionMode.Failure;
|
||||
|
||||
//OK, we do server switching for scripting for SQL/Analysis Server execution here
|
||||
RunType runType = executionInfo.RunType;
|
||||
if (IsScripting(runType))
|
||||
{
|
||||
if (!PreProcessScripting(executionInfo, out executionResult))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (DataContainer != null)
|
||||
{
|
||||
//we take over execution here. We substitute the server here for AMO and SQL
|
||||
//dialogs
|
||||
if (DataContainer.ContainerServerType == CDataContainer.ServerType.SQL)
|
||||
{
|
||||
ExecuteForSql(executionInfo, out executionResult);
|
||||
return false;//execution of the entire control was done here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// call virtual function to do regular execution
|
||||
return DoPreProcessExecution(executionInfo.RunType, out executionResult);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// whether we own our DataContainer or not. Depending on this value it will or won't be
|
||||
/// disposed in our Dispose method
|
||||
/// </summary>
|
||||
protected virtual bool OwnDataContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
//by default we own it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// called by IExecutionAwareSqlControlCollection.PreProcessExecution to enable derived
|
||||
/// classes to take over execution of the dialog and do entire execution in this method
|
||||
/// rather than having the framework to execute dialog views one by one.
|
||||
///
|
||||
/// NOTE: it might be called from non-UI thread
|
||||
/// </summary>
|
||||
/// <param name="runType"></param>
|
||||
/// <param name="executionResult"></param>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false if everything,
|
||||
/// has been done by this function
|
||||
/// </returns>
|
||||
protected virtual bool DoPreProcessExecution(RunType runType, out ExecutionMode executionResult)
|
||||
{
|
||||
//ask the framework to do normal execution by calling OnRunNOw methods
|
||||
//of the views one by one
|
||||
executionResult = ExecutionMode.Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called before dialog's host executes OnReset method on all panels in the dialog one by one
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false if everything
|
||||
/// has been done by this function
|
||||
/// </returns>
|
||||
/// <returns></returns>
|
||||
protected virtual bool DoPreProcessReset()
|
||||
{
|
||||
if ((this.dataContainer != null) && this.dataContainer.IsNewObject)
|
||||
{
|
||||
this.dataContainer.Reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called after dialog's host executes OnReset method on all panels in the dialog one by one
|
||||
/// NOTE: it might be called from worker thread
|
||||
/// </summary>
|
||||
protected virtual void DoPostProcessReset()
|
||||
{
|
||||
//nothing in the base class
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to intercept scripting operation
|
||||
/// </summary>
|
||||
/// <param name="executionInfo"></param>
|
||||
/// <param name="executionResult"></param>
|
||||
/// <returns>
|
||||
/// true if regular execution should take place, false the script
|
||||
/// has been created by this function
|
||||
/// </returns>
|
||||
protected virtual bool PreProcessScripting(PreProcessExecutionInfo executionInfo, out ExecutionMode executionResult)
|
||||
{
|
||||
//we don't do anything here, but we enable derived classes to do something...
|
||||
executionResult = ExecutionMode.Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CDataContainer accessor
|
||||
/// </summary>
|
||||
protected CDataContainer DataContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.dataContainer;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.dataContainer = value;
|
||||
this.ownDataContainer = OwnDataContainer; //cache the value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// SMO Server connection that MUST be used for all enumerator calls
|
||||
// /// We'll get this object out of CDataContainer, that must be initialized
|
||||
// /// property by the initialization code
|
||||
// /// </summary>
|
||||
protected ServerConnection ServerConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.serverConnection == null && this.dataContainer != null &&
|
||||
this.dataContainer.ContainerServerType == CDataContainer.ServerType.SQL)
|
||||
{
|
||||
this.serverConnection = this.dataContainer.ServerConnection;
|
||||
}
|
||||
|
||||
//it CAN be null here if dataContainer hasn't been set or the container type is non SQL
|
||||
return this.serverConnection;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks whether given run time represents one of scripting options
|
||||
/// </summary>
|
||||
/// <param name="runType"></param>
|
||||
/// <returns></returns>
|
||||
protected static bool IsScripting(RunType runType)
|
||||
{
|
||||
return(runType != RunType.RunNow && runType != RunType.RunNowAndExit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// calls DoPreProcessExecution and if it returns false, then it will execute all initialized views
|
||||
/// one by one.
|
||||
/// This method allows derived classes to do both preprocessing and normal execution in a way
|
||||
/// that the framework would normally do. Use this method for special cases when derived class
|
||||
/// should really handle entire execution while doing exactly the same actions as the framework
|
||||
/// would do
|
||||
/// </summary>
|
||||
/// <param name="runType"></param>
|
||||
/// <returns>
|
||||
/// result of the execution. It will let exception fly out if it was raised during execution
|
||||
/// </returns>
|
||||
protected ExecutionMode DoPreProcessExecutionAndRunViews(RunType runType)
|
||||
{
|
||||
ExecutionMode executionResult;
|
||||
if (DoPreProcessExecution(runType, out executionResult))
|
||||
{
|
||||
//true return value means that we need to do execution ourselves
|
||||
//executionResult = PanelExecutionHandler.Run(runType, this);
|
||||
}
|
||||
|
||||
return executionResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// determines whether we need to substitute SMO/AMO server objects with the
|
||||
/// temporary ones while doing scripting
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool NeedToSwitchServer
|
||||
{
|
||||
get
|
||||
{
|
||||
ServerSwitchingAttribute switchingAttrib =
|
||||
Utils.GetCustomAttribute(this, typeof(ServerSwitchingAttribute)) as ServerSwitchingAttribute;
|
||||
|
||||
if (DataContainer == null)
|
||||
{
|
||||
if (switchingAttrib != null)
|
||||
{
|
||||
return switchingAttrib.NeedToSwtichServerObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
if (DataContainer.ContainerServerType == CDataContainer.ServerType.SQL)
|
||||
{
|
||||
if (switchingAttrib != null)
|
||||
{
|
||||
return switchingAttrib.NeedToSwtichServerObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;//switch by default in SQL case
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual ServerConnection GetServerConnectionForScript()
|
||||
{
|
||||
return this.dataContainer.Server.ConnectionContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// builds a script string from SMO string collections. This function should
|
||||
/// probably be moved to SMO. Also we need a way to specify the tsql batch
|
||||
/// separator which for now is GO
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string BuildSqlScript()
|
||||
{
|
||||
SqlSmoObject sqlDialogSubject = null;
|
||||
try
|
||||
{
|
||||
sqlDialogSubject = this.DataContainer.SqlDialogSubject;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
//We may not have a valid dialog subject here (such as if the object hasn't been created yet)
|
||||
//so in that case we'll just ignore it as that's a normal scenario.
|
||||
}
|
||||
|
||||
StringCollection sc = GetServerConnectionForScript().CapturedSql.Text;
|
||||
//Scripting may happen on either the server ExecutionManager or the
|
||||
//ExecutionManager of the object itself. So we make sure to check
|
||||
//the subject text if the server ExecutionManager didn't have any
|
||||
//scripts after doing the scripting
|
||||
if (sc.Count == 0 && sqlDialogSubject != null)
|
||||
{
|
||||
sc = sqlDialogSubject.ExecutionManager.ConnectionContext.CapturedSql.Text;
|
||||
}
|
||||
int i;
|
||||
StringBuilder script = new StringBuilder(4096);
|
||||
|
||||
if (sc != null)
|
||||
{
|
||||
for (i = 0; i < sc.Count; i ++)
|
||||
{
|
||||
script.AppendFormat("{0}\r\nGO\r\n", sc[i].ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return script.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// called when we need to script a Sql server dlg.
|
||||
/// </summary>
|
||||
/// <param name="executionInfo"></param>
|
||||
/// <param name="executionResult"></param>
|
||||
private void ExecuteForSql(PreProcessExecutionInfo executionInfo, out ExecutionMode executionResult)
|
||||
{
|
||||
Microsoft.SqlServer.Management.Smo.Server oldServer = null;
|
||||
if (NeedToSwitchServer)
|
||||
{
|
||||
// We use a new instance of the SMO Server object every time we script
|
||||
// so that any changes that are made to the SMO server while scripting are
|
||||
// not kept when the script operation is completed.
|
||||
oldServer = DataContainer.Server;
|
||||
|
||||
//BUGBUG - see if we can use copy ctor instead
|
||||
DataContainer.Server = new Microsoft.SqlServer.Management.Smo.Server(DataContainer.ServerConnection);
|
||||
}
|
||||
|
||||
String szScript = null;
|
||||
bool isScripting = IsScripting(executionInfo.RunType);
|
||||
var executionModeOriginal = GetServerConnectionForScript().SqlExecutionModes;
|
||||
//For Azure the ExecutionManager is different depending on which ExecutionManager
|
||||
//used - one at the Server level and one at the Database level. So to ensure we
|
||||
//don't use the wrong execution mode we need to set the mode for both (for on-prem
|
||||
//this will essentially be a no-op)
|
||||
SqlExecutionModes subjectExecutionModeOriginal = executionModeOriginal;
|
||||
SqlSmoObject sqlDialogSubject = null;
|
||||
try
|
||||
{
|
||||
sqlDialogSubject = this.DataContainer.SqlDialogSubject;
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
//We may not have a valid dialog subject here (such as if the object hasn't been created yet)
|
||||
//so in that case we'll just ignore it as that's a normal scenario.
|
||||
}
|
||||
|
||||
if (sqlDialogSubject != null)
|
||||
{
|
||||
subjectExecutionModeOriginal = sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes;
|
||||
}
|
||||
try
|
||||
{
|
||||
SqlExecutionModes newMode = isScripting
|
||||
? SqlExecutionModes.CaptureSql
|
||||
: SqlExecutionModes.ExecuteSql;
|
||||
//now, do the execution itself
|
||||
GetServerConnectionForScript().SqlExecutionModes = newMode;
|
||||
if (sqlDialogSubject != null)
|
||||
{
|
||||
sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes = newMode;
|
||||
}
|
||||
|
||||
executionResult = DoPreProcessExecutionAndRunViews(executionInfo.RunType);
|
||||
|
||||
if (isScripting)
|
||||
{
|
||||
if (executionResult == ExecutionMode.Success)
|
||||
{
|
||||
szScript = BuildSqlScript();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
GetServerConnectionForScript().SqlExecutionModes = executionModeOriginal;
|
||||
|
||||
if (isScripting)
|
||||
{
|
||||
GetServerConnectionForScript().CapturedSql.Clear();
|
||||
}
|
||||
|
||||
if (sqlDialogSubject != null)
|
||||
{
|
||||
sqlDialogSubject.ExecutionManager.ConnectionContext.SqlExecutionModes = subjectExecutionModeOriginal;
|
||||
if (isScripting)
|
||||
{
|
||||
sqlDialogSubject.ExecutionManager.ConnectionContext.CapturedSql.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
//see if we need to restore the server
|
||||
if (oldServer != null)
|
||||
{
|
||||
DataContainer.Server = oldServer;
|
||||
}
|
||||
}
|
||||
|
||||
if (isScripting)
|
||||
{
|
||||
executionInfo.Script = szScript;
|
||||
}
|
||||
}
|
||||
|
||||
// #region ICustomAttributeProvider
|
||||
// object[] System.Reflection.ICustomAttributeProvider.GetCustomAttributes(bool inherit)
|
||||
// {
|
||||
// //we merge attributes from 2 sources: type attributes and the derived classes, giving preference
|
||||
// //to the derived classes
|
||||
// return GetMergedArray(DoGetCustomAttributes(inherit), GetType().GetCustomAttributes(inherit));
|
||||
// }
|
||||
// object[] System.Reflection.ICustomAttributeProvider.GetCustomAttributes(Type attributeType, bool inherit)
|
||||
// {
|
||||
// //we merge attributes from 2 sources: type attributes and the derived classes, giving preference
|
||||
// //to the derived classes
|
||||
// return GetMergedArray(DoGetCustomAttributes(attributeType, inherit),
|
||||
// GetType().GetCustomAttributes(attributeType, inherit));
|
||||
// }
|
||||
// bool System.Reflection.ICustomAttributeProvider.IsDefined(Type attributeType, bool inherit)
|
||||
// {
|
||||
// //we merge attributes from 2 sources: type attributes and the derived classes, giving preference
|
||||
// //to the derived classes
|
||||
// if (!DoIsDefined(attributeType, inherit))
|
||||
// {
|
||||
// return GetType().IsDefined(attributeType, inherit);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// #endregion
|
||||
// #region ICustomAttributeProvider helpers
|
||||
// protected virtual object[] DoGetCustomAttributes(bool inherit)
|
||||
// {
|
||||
// return GetMergedArray(DoGetCustomAttributes(typeof(ScriptTypeAttribute), inherit),
|
||||
// DoGetCustomAttributes(typeof(DialogScriptableAttribute), inherit));
|
||||
// }
|
||||
// protected virtual object[] DoGetCustomAttributes(Type attributeType, bool inherit)
|
||||
// {
|
||||
// //if the type specifies this attribute, we don't bother - it overrides
|
||||
// //our behavior
|
||||
// object[] typeAttribs = GetType().GetCustomAttributes(attributeType, inherit);
|
||||
// if (typeAttribs != null && typeAttribs.Length > 0)
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// //we expose default custom attribute for script type
|
||||
// if (attributeType.Equals(typeof(ScriptTypeAttribute)))
|
||||
// {
|
||||
// string scriptType = ScriptType;
|
||||
// if (scriptType != null)
|
||||
// {
|
||||
// return new object[] {new ScriptTypeAttribute(scriptType)};
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// else if (attributeType.Equals(typeof(DialogScriptableAttribute)))
|
||||
// {
|
||||
// bool canScriptToWindow = true;
|
||||
// bool canScriptToFile = true;
|
||||
// bool canScriptToClipboard = true;
|
||||
// bool canScriptToJob = true;
|
||||
// GetScriptableOptions(out canScriptToWindow,
|
||||
// out canScriptToFile,
|
||||
// out canScriptToClipboard,
|
||||
// out canScriptToJob);
|
||||
// return new object[] {new DialogScriptableAttribute(canScriptToWindow,
|
||||
// canScriptToFile,
|
||||
// canScriptToClipboard,
|
||||
// canScriptToJob)};
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// protected virtual bool DoIsDefined(Type attributeType, bool inherit)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// detects whether script types are applicable for this dlg or not. By default
|
||||
// /// the framework relies on DialogScriptableAttribute set on the dlg class and won't
|
||||
// /// call this method if the attribute is specified
|
||||
// /// By default we assume that all script types are enabled
|
||||
// /// </summary>
|
||||
// /// <param name="?"></param>
|
||||
// /// <param name="?"></param>
|
||||
// /// <param name="?"></param>
|
||||
// protected virtual void GetScriptableOptions(out bool canScriptToWindow,
|
||||
// out bool canScriptToFile,
|
||||
// out bool canScriptToClipboard,
|
||||
// out bool canScriptToJob)
|
||||
// {
|
||||
// canScriptToWindow = canScriptToFile = canScriptToClipboard = canScriptToJob = true;
|
||||
// }
|
||||
// #endregion
|
||||
// protected IServiceProvider ServiceProvider
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// if (this.serviceProvider == null)
|
||||
// {
|
||||
// STrace.Assert(false, "Cannot work without service provider!");
|
||||
// STrace.LogExThrow();
|
||||
// //BUGBUG - should we have our own exception here?
|
||||
// throw new InvalidOperationException();
|
||||
// }
|
||||
// return this.serviceProvider;
|
||||
// }
|
||||
// }
|
||||
// /// <summary>
|
||||
// /// returns combination of the given 2 arrays
|
||||
// /// </summary>
|
||||
// /// <param name="array1"></param>
|
||||
// /// <param name="array2"></param>
|
||||
// /// <returns></returns>
|
||||
// protected object[] GetMergedArray(object[] array1, object[] array2)
|
||||
// {
|
||||
// if (array1 == null)
|
||||
// {
|
||||
// return array2;
|
||||
// }
|
||||
// else if (array2 == null)
|
||||
// {
|
||||
// return array1;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// object[] finalReturnValue = new object[array1.Length + array2.Length];
|
||||
// array1.CopyTo(finalReturnValue, 0);
|
||||
// array2.CopyTo(finalReturnValue, array1.Length);
|
||||
// return finalReturnValue;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Collections;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for the mapping of objects that implement IProgressItem to individual items in the
|
||||
/// progress dialog.
|
||||
/// </summary>
|
||||
public class ProgressItemCollection : ICollection
|
||||
{
|
||||
#region internal helper classes
|
||||
/// <summary>
|
||||
/// Allows us to map an action to its index in the progress dialog.
|
||||
/// </summary>
|
||||
public class ActionIndexMap
|
||||
{
|
||||
/// <summary>
|
||||
/// action
|
||||
/// </summary>
|
||||
public IProgressItem Action;
|
||||
/// <summary>
|
||||
/// index
|
||||
/// </summary>
|
||||
public int Index;
|
||||
|
||||
public ActionIndexMap(IProgressItem action)
|
||||
{
|
||||
this.Action = action;
|
||||
// index isn't known yet
|
||||
this.Index = -1;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region private data members
|
||||
/// <summary>
|
||||
/// list of actions we will perform.
|
||||
/// </summary>
|
||||
private ArrayList actions = new ArrayList();
|
||||
#endregion
|
||||
|
||||
#region construction
|
||||
public ProgressItemCollection()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region properties
|
||||
|
||||
private bool closeOnUserCancel = false;
|
||||
/// <summary>
|
||||
/// Indicates whether to close the dialog immediately if the user cancels an operation
|
||||
/// </summary>
|
||||
public bool CloseOnUserCancel
|
||||
{
|
||||
get
|
||||
{
|
||||
return closeOnUserCancel;
|
||||
}
|
||||
set
|
||||
{
|
||||
closeOnUserCancel = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool automaticClose = false;
|
||||
/// <summary>
|
||||
/// Indicates whether to automatically close the dialog when all actions are complete
|
||||
/// successfully.
|
||||
/// </summary>
|
||||
public bool CloseOnSuccessfulCompletion
|
||||
{
|
||||
get
|
||||
{
|
||||
return automaticClose;
|
||||
}
|
||||
set
|
||||
{
|
||||
automaticClose = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool quitOnError = false;
|
||||
/// <summary>
|
||||
/// Indicates whether the operation should be terminated if any individual step fails.
|
||||
/// </summary>
|
||||
public bool QuitOnError
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.quitOnError;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.quitOnError = value;
|
||||
}
|
||||
}
|
||||
private OperationStatus operationStatus = OperationStatus.Invalid;
|
||||
/// <summary>
|
||||
/// Indicates the status of the operation.
|
||||
/// </summary>
|
||||
public OperationStatus OperationStatus
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.operationStatus;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Progress object this action collection will work with
|
||||
/// </summary>
|
||||
private IProgress progress = null;
|
||||
public IProgress Progress
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.progress;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (this.progress != value)
|
||||
{
|
||||
this.progress = value;
|
||||
if (this.progress != null)
|
||||
{
|
||||
// add the actions to the progress dialog, and
|
||||
// fixup our event handler
|
||||
FixUpActionsToProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region public overrides
|
||||
/// <summary>
|
||||
/// Generate a string representaion of this object. It will convert all of it's IProgressItem members
|
||||
/// to strings in a new line.
|
||||
/// </summary>
|
||||
/// <returns>string description of the actions this object contains</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
// if there are no actions then just return the default ToString
|
||||
if (this.actions == null || this.actions.Count == 0)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// convert all of the actions to strings on their own line
|
||||
StringBuilder sb = new StringBuilder(((ActionIndexMap)actions[0]).Action.ToString());
|
||||
for (int i = 1; i < this.actions.Count; i++)
|
||||
{
|
||||
sb.AppendFormat(CultureInfo.InvariantCulture, "\r\n{0}", ((ActionIndexMap)actions[i]).Action.ToString());
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ICollection implementation
|
||||
/// <summary>
|
||||
/// Gets the number of actions in this collection
|
||||
/// </summary>
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.actions.Count;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// not supported
|
||||
/// </summary>
|
||||
public bool IsSynchronized
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// not supported
|
||||
/// </summary>
|
||||
public object SyncRoot
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
public void CopyTo(IProgressItem[] array, int start)
|
||||
{
|
||||
this.actions.CopyTo(array, start);
|
||||
}
|
||||
public void CopyTo(Array array, int start)
|
||||
{
|
||||
this.actions.CopyTo(array, start);
|
||||
}
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return this.actions.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region public methods
|
||||
/// <summary>
|
||||
/// Add an action to the collection
|
||||
/// </summary>
|
||||
/// <param name="action">action to be added</param>
|
||||
public void AddAction(IProgressItem action)
|
||||
{
|
||||
ActionIndexMap map = new ActionIndexMap(action);
|
||||
this.actions.Add(map);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region internal implementation
|
||||
/// <summary>
|
||||
/// delegate called when the progress dialog wants us to perform work on a new thread.
|
||||
/// </summary>
|
||||
private void DoWorkOnThread()
|
||||
{
|
||||
if (this.Progress == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.Name = "Worker thread for " + progress.GetType();
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{ }
|
||||
|
||||
// default to succeeded.
|
||||
operationStatus = OperationStatus.Success;
|
||||
|
||||
// carry out each action.
|
||||
foreach (ActionIndexMap map in this.actions)
|
||||
{
|
||||
// abort if the user has decided to cancel.
|
||||
if (this.Progress.IsAborted)
|
||||
{
|
||||
this.Progress.UpdateActionStatus(map.Index, ProgressStatus.Aborted);
|
||||
operationStatus = OperationStatus.Aborted;
|
||||
break;
|
||||
}
|
||||
ProgressStatus stepStatus = ProgressStatus.Invalid;
|
||||
try
|
||||
{
|
||||
// perform the action.
|
||||
stepStatus = map.Action.DoAction(this, map.Index);
|
||||
this.Progress.UpdateActionStatus(map.Index, stepStatus);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// fail the step with errors, add the error messages to the control.
|
||||
this.Progress.AddActionException(map.Index, e);
|
||||
this.Progress.UpdateActionStatus(map.Index, ProgressStatus.Error);
|
||||
stepStatus = ProgressStatus.Error;
|
||||
}
|
||||
if (stepStatus == ProgressStatus.Error)
|
||||
{
|
||||
// see if we're supposed to fail if any step fails
|
||||
if (this.QuitOnError == true)
|
||||
{
|
||||
// fail and quit
|
||||
this.operationStatus = OperationStatus.Error;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.operationStatus = OperationStatus.CompletedWithErrors;
|
||||
}
|
||||
}
|
||||
else if (stepStatus != ProgressStatus.Success)
|
||||
{
|
||||
this.operationStatus = OperationStatus.CompletedWithErrors;
|
||||
}
|
||||
}
|
||||
|
||||
// tell the dialog we're finishing.
|
||||
this.Progress.WorkerThreadExiting(operationStatus);
|
||||
|
||||
// close the dialog if asked to. We have to put this after
|
||||
// the WorkerThreadExiting call because the progress dialog
|
||||
// won't allow itself to be closed until worker thread says
|
||||
// it's finished.
|
||||
if ((this.CloseOnSuccessfulCompletion && (this.operationStatus == OperationStatus.Success)) ||
|
||||
(this.CloseOnUserCancel && this.Progress.IsAborted))
|
||||
{
|
||||
//((Form)this.Progress).BeginInvoke(new CloseProgressWindowCallback(CloseProgressWindowHandler), new object[] { this.Progress });
|
||||
}
|
||||
}
|
||||
|
||||
private delegate void CloseProgressWindowCallback(IProgress progress);
|
||||
private void CloseProgressWindowHandler(IProgress progress)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the actions to an IProgress interface.
|
||||
/// </summary>
|
||||
private void FixUpActionsToProgress()
|
||||
{
|
||||
if (this.Progress == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// add actions
|
||||
foreach (ActionIndexMap map in this.actions)
|
||||
{
|
||||
map.Index = this.Progress.AddAction(map.Action.ToString());
|
||||
}
|
||||
// add our delegate
|
||||
this.Progress.WorkerThreadStart = new ThreadStart(this.DoWorkOnThread);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration for status of individual actions
|
||||
/// </summary>
|
||||
public enum ProgressStatus
|
||||
{
|
||||
Invalid = -1,
|
||||
NotStarted, // Not started
|
||||
InProgress, // In progress
|
||||
Success, // Completed
|
||||
SuccessWithInfo, // Completed, display additional info
|
||||
Warning, // Completed with warning, display exceptions
|
||||
Error, // Not completed because of error, display exceptions
|
||||
Aborted, // Aborted
|
||||
RolledBack, // Rolled back because of a subsequent error
|
||||
StatusCount // = Number of status values - For validation only
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for status of the overall operation
|
||||
/// </summary>
|
||||
public enum OperationStatus
|
||||
{
|
||||
Invalid = -1,
|
||||
InProgress, // In progress
|
||||
Success, // Completed successfully
|
||||
CompletedWithErrors, // Completed with non-fatal errors
|
||||
Error, // Not completed because of error
|
||||
Aborted, // Abort complete
|
||||
StatusCount // = Number of status values - For validation only
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface defining core functionality of a progress control container
|
||||
///
|
||||
/// NOTE: Refer to the comments for each method/property individually to determine
|
||||
/// whether it is thread-safe and may be used from the worker thread. Also note
|
||||
/// that some members are asynchronous.
|
||||
/// </summary>
|
||||
public interface IProgress
|
||||
{
|
||||
//-----------
|
||||
// Properties
|
||||
//-----------
|
||||
|
||||
/// <summary>
|
||||
/// The property that determines if the user should be allowed to abort the
|
||||
/// operation. The default value is true.
|
||||
///
|
||||
/// NOTE: This property is not thread safe. Set from UI thread.
|
||||
/// </summary>
|
||||
bool AllowAbort
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The confirmation prompt to display if the user hits the Abort button.
|
||||
/// The abort prompt should be worded such that the abort is confirmed
|
||||
/// if the user hits "Yes".
|
||||
///
|
||||
/// NOTE: This property is not thread safe. Set from UI thread.
|
||||
/// </summary>
|
||||
string AbortPrompt
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ThreadStart delegate. The method referenced by this delegate is run by
|
||||
/// the worker thread to perform the operation.
|
||||
///
|
||||
/// NOTE: This property is not thread safe. Set from UI thread.
|
||||
/// </summary>
|
||||
ThreadStart WorkerThreadStart
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aborted status of the operation.
|
||||
///
|
||||
/// NOTE: This property is thread safe and may be called from the worker
|
||||
/// thread. Accessing this property may cause caller to block if UI
|
||||
/// is waiting for user confirmation of abort.
|
||||
/// </summary>
|
||||
bool IsAborted
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This property determines whether updates should be allowed for
|
||||
/// actions. If this is set to false, any calls that are made
|
||||
/// to add or update an action are ignored. The default value is true.
|
||||
///
|
||||
/// NOTE: This property is thread safe and may be called from the worker
|
||||
/// thread.
|
||||
/// </summary>
|
||||
bool ActionUpdateEnabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
//--------
|
||||
// Methods
|
||||
//--------
|
||||
|
||||
/// <summary>
|
||||
/// Add an action to the displayed list of actions. Actions are
|
||||
/// displayed in the order they are added. An action can be referenced
|
||||
/// in future calls using the zero-based index that is returned.
|
||||
///
|
||||
/// The description must be a non-empty string.
|
||||
///
|
||||
/// NOTE: This method is not thread safe. Call from the UI thread.
|
||||
/// </summary>
|
||||
/// <param name="description">Description of the action</param>
|
||||
/// <returns>The index of the newly added action.</returns>
|
||||
int AddAction(string description);
|
||||
|
||||
/// <summary>
|
||||
/// Add an action to the displayed list of actions. This is meant
|
||||
/// to be called from the worker thread to add an action after
|
||||
/// the operation is already in progress.
|
||||
///
|
||||
/// Actions are displayed in the order they are added. Use the
|
||||
/// zero-based index of the action based on past actions added
|
||||
/// to reference it in future calls. The description must be a
|
||||
/// non-empty string.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="description">Description of the action</param>
|
||||
void AddActionDynamic(string description);
|
||||
|
||||
/// <summary>
|
||||
/// Update the description of an action
|
||||
///
|
||||
/// The description must be a non-empty string.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="description">New description of the action</param>
|
||||
void UpdateActionDescription(int actionIndex, string description);
|
||||
|
||||
/// <summary>
|
||||
/// Update the status of an action
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="status">New status of the action</param>
|
||||
void UpdateActionStatus(int actionIndex, ProgressStatus status);
|
||||
|
||||
/// <summary>
|
||||
/// Update the progress of an action in terms of percentage complete
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="percentComplete">Percentage of the action that is complete (0-100)</param>
|
||||
void UpdateActionProgress(int actionIndex, int percentComplete);
|
||||
|
||||
/// <summary>
|
||||
/// Update the progress of an action with a text description of
|
||||
/// the progress
|
||||
///
|
||||
/// The description must be a non-empty string.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="description">Description of progress</param>
|
||||
void UpdateActionProgress(int actionIndex, string description);
|
||||
|
||||
/// <summary>
|
||||
/// Add an exception to an action
|
||||
///
|
||||
/// Exceptions are displayed in the action grid only for actions
|
||||
/// with "Error" or "Warning" status.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="e">Exception to be added</param>
|
||||
void AddActionException(int actionIndex, Exception e);
|
||||
|
||||
/// <summary>
|
||||
/// Add an info string to an action in the progress report control
|
||||
///
|
||||
/// Information strings are displayed in the action grid only for
|
||||
/// actions with "SuccessWithInfo" status. The info string must
|
||||
/// be a non-empty string. It should not be formatted or contain
|
||||
/// newline characters.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="actionIndex">Index of the action</param>
|
||||
/// <param name="infoString">Information string to be added</param>
|
||||
void AddActionInfoString(int actionIndex, string infoString);
|
||||
|
||||
/// <summary>
|
||||
/// Call this method when the worker thread performing the operation
|
||||
/// is about to exit. The final result of the operation is supplied in
|
||||
/// the form of a OperationStatus value.
|
||||
///
|
||||
/// NOTE: This method is thread safe and asynchronous. It may be
|
||||
/// called from the worker thread.
|
||||
/// </summary>
|
||||
/// <param name="result">Result of the operation</param>
|
||||
void WorkerThreadExiting(OperationStatus result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumeration for status of the progress report control w.r.t the operation
|
||||
/// </summary>
|
||||
[System.Runtime.InteropServices.ComVisible(false)]
|
||||
public enum ProgressCtrlStatus
|
||||
{
|
||||
Invalid = -1,
|
||||
InProgress, // In progress
|
||||
Success, // Completed successfully
|
||||
CompletedWithErrors, // Completed with non-fatal errors
|
||||
Error, // Not completed because of error
|
||||
Aborting, // User clicked "Abort", aborting operation
|
||||
Aborted, // Abort complete
|
||||
Closed, // User clicked "Close"
|
||||
StatusCount // = Number of status values - For validation only
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate used with ProgressCtrlStatusChanged event.
|
||||
/// </summary>
|
||||
public delegate void ProgressCtrlStatusChangedEventHandler(object source, ProgressCtrlStatusChangedEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// EventArgs class for use with ProgressCtrlStatusChanged event
|
||||
/// </summary>
|
||||
sealed public class ProgressCtrlStatusChangedEventArgs : EventArgs
|
||||
{
|
||||
//------------------
|
||||
// Public Properties
|
||||
//------------------
|
||||
public ProgressCtrlStatus Status
|
||||
{
|
||||
get { return m_status; }
|
||||
set { m_status = value; }
|
||||
}
|
||||
|
||||
//-------------
|
||||
// Private Data
|
||||
//-------------
|
||||
private ProgressCtrlStatus m_status = ProgressCtrlStatus.Invalid;
|
||||
}
|
||||
|
||||
// Enumeration for progress action grid columns
|
||||
internal enum ProgressActionColumn
|
||||
{
|
||||
Invalid = -1,
|
||||
ActionStatusBitmap,
|
||||
ActionDescription,
|
||||
ActionStatusText,
|
||||
ActionMessage,
|
||||
ActionColumnCount // = Number of columns - For validation only
|
||||
}
|
||||
|
||||
// Enumeration for progress action display filter
|
||||
internal enum ProgressActionDisplayMode
|
||||
{
|
||||
Invalid = -1,
|
||||
DisplayAllActions,
|
||||
DisplayErrors,
|
||||
DisplaySuccess,
|
||||
DisplayWarnings,
|
||||
ActionDisplayModeCount // = Number of display modes - For validation only
|
||||
}
|
||||
}
|
||||
23
src/Microsoft.SqlTools.ServiceLayer/Agent/Common/RunType.cs
Normal file
23
src/Microsoft.SqlTools.ServiceLayer/Agent/Common/RunType.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// what type of actions does the worker know to execute
|
||||
/// </summary>
|
||||
public enum RunType
|
||||
{
|
||||
RunNow = 0,
|
||||
RunNowAndExit,
|
||||
ScriptToFile,
|
||||
ScriptToWindow,
|
||||
ScriptToClipboard,
|
||||
ScriptToJob
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom attribute that can be applied on particular DB commander to
|
||||
/// indicate whether the base class should switch SMO servers before
|
||||
/// execution or not.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public sealed class ServerSwitchingAttribute : Attribute
|
||||
{
|
||||
private bool needToSwitch = false;
|
||||
|
||||
private ServerSwitchingAttribute() {}
|
||||
|
||||
public ServerSwitchingAttribute(bool needToSwitchServer)
|
||||
{
|
||||
this.needToSwitch = needToSwitchServer;
|
||||
}
|
||||
|
||||
public bool NeedToSwtichServerObject
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.needToSwitch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user