init
This commit is contained in:
541
Common/DTS.Common/Utils/BusyWaitAnimation.cs
Normal file
541
Common/DTS.Common/Utils/BusyWaitAnimation.cs
Normal file
@@ -0,0 +1,541 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used to display spinning animation circle to indicate
|
||||
/// system is busy with background process
|
||||
///
|
||||
/// Adapting from open source found via Google so it doesn' conform to our
|
||||
/// coding formatting or commenting rules
|
||||
/// </summary>
|
||||
public class BusyWaitAnimation
|
||||
{
|
||||
|
||||
// Constants =========================================================
|
||||
private const double NumberOfDegreesInCircle = 360;
|
||||
private const double NumberOfDegreesInHalfCircle = NumberOfDegreesInCircle / 2;
|
||||
private const int DefaultInnerCircleRadius = 8;
|
||||
private const int DefaultOuterCircleRadius = 10;
|
||||
private const int DefaultNumberOfSpoke = 10;
|
||||
private const int DefaultSpokeThickness = 4;
|
||||
private readonly Color _defaultColor = Color.DarkGray;
|
||||
|
||||
private const int MacOsxInnerCircleRadius = 5;
|
||||
private const int MacOsxOuterCircleRadius = 11;
|
||||
private const int MacOsxNumberOfSpoke = 12;
|
||||
private const int MacOsxSpokeThickness = 2;
|
||||
|
||||
private const int FireFoxInnerCircleRadius = 6;
|
||||
private const int FireFoxOuterCircleRadius = 7;
|
||||
private const int FireFoxNumberOfSpoke = 9;
|
||||
private const int FireFoxSpokeThickness = 4;
|
||||
|
||||
private const int Ie7InnerCircleRadius = 8;
|
||||
private const int Ie7OuterCircleRadius = 9;
|
||||
private const int Ie7NumberOfSpoke = 24;
|
||||
private const int Ie7SpokeThickness = 4;
|
||||
|
||||
// Enumeration =======================================================
|
||||
public enum StylePresets
|
||||
{
|
||||
MacOsx,
|
||||
Firefox,
|
||||
Ie7,
|
||||
Custom
|
||||
}
|
||||
|
||||
// Attributes ========================================================
|
||||
private readonly DispatcherTimer _timer;
|
||||
private bool _isTimerActive;
|
||||
private int _numberOfSpoke;
|
||||
private int _spokeThickness;
|
||||
private int _progressValue;
|
||||
private int _outerCircleRadius;
|
||||
private int _innerCircleRadius;
|
||||
private PointF _centerPoint;
|
||||
private Color _color;
|
||||
private Color[] _colors;
|
||||
private double[] _angles;
|
||||
private StylePresets _stylePreset;
|
||||
|
||||
// Properties ========================================================
|
||||
/// <summary>
|
||||
/// Gets or sets the lightest color of the circle.
|
||||
/// </summary>
|
||||
/// <value>The lightest color of the circle.</value>
|
||||
[TypeConverter("System.Drawing.ColorConverter"),
|
||||
Category("LoadingCircle"),
|
||||
Description("Sets the color of spoke.")]
|
||||
public Color Color
|
||||
{
|
||||
get => _color;
|
||||
set
|
||||
{
|
||||
_color = value;
|
||||
|
||||
GenerateColorsPallet();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Background color
|
||||
/// </summary>
|
||||
public Color BgColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the outer circle radius.
|
||||
/// </summary>
|
||||
/// <value>The outer circle radius.</value>
|
||||
[Description("Gets or sets the radius of outer circle."),
|
||||
Category("LoadingCircle")]
|
||||
public int OuterCircleRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_outerCircleRadius == 0)
|
||||
{ _outerCircleRadius = DefaultOuterCircleRadius; }
|
||||
|
||||
return _outerCircleRadius;
|
||||
}
|
||||
set
|
||||
{
|
||||
_outerCircleRadius = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the inner circle radius.
|
||||
/// </summary>
|
||||
/// <value>The inner circle radius.</value>
|
||||
[Description("Gets or sets the radius of inner circle."),
|
||||
Category("LoadingCircle")]
|
||||
public int InnerCircleRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_innerCircleRadius == 0)
|
||||
_innerCircleRadius = DefaultInnerCircleRadius;
|
||||
|
||||
return _innerCircleRadius;
|
||||
}
|
||||
set
|
||||
{
|
||||
_innerCircleRadius = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of spoke.
|
||||
/// </summary>
|
||||
/// <value>The number of spoke.</value>
|
||||
[Description("Gets or sets the number of spoke."),
|
||||
Category("LoadingCircle")]
|
||||
public int NumberSpoke
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_numberOfSpoke == 0)
|
||||
_numberOfSpoke = DefaultNumberOfSpoke;
|
||||
|
||||
return _numberOfSpoke;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_numberOfSpoke != value && _numberOfSpoke > 0)
|
||||
{
|
||||
_numberOfSpoke = value;
|
||||
GenerateColorsPallet();
|
||||
GetSpokesAngles();
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="T:LoadingCircle"/> is active.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
|
||||
[Description("Gets or sets the number of spoke."),
|
||||
Category("LoadingCircle")]
|
||||
public bool Active
|
||||
{
|
||||
get => _isTimerActive;
|
||||
set
|
||||
{
|
||||
_isTimerActive = value;
|
||||
ActiveTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the spoke thickness.
|
||||
/// </summary>
|
||||
/// <value>The spoke thickness.</value>
|
||||
[Description("Gets or sets the thickness of a spoke."),
|
||||
Category("LoadingCircle")]
|
||||
public int SpokeThickness
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_spokeThickness <= 0)
|
||||
_spokeThickness = DefaultSpokeThickness;
|
||||
|
||||
return _spokeThickness;
|
||||
}
|
||||
set
|
||||
{
|
||||
_spokeThickness = value;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation speed.
|
||||
/// </summary>
|
||||
/// <value>The rotation speed.</value>
|
||||
[Description("Gets or sets the rotation speed. Higher the slower."),
|
||||
Category("LoadingCircle")]
|
||||
public long RotationSpeed
|
||||
{
|
||||
get => _timer.Interval.Ticks;
|
||||
set
|
||||
{
|
||||
if (value > 0)
|
||||
_timer.Interval = TimeSpan.FromMilliseconds(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quickly sets the style to one of these presets, or a custom style if desired
|
||||
/// </summary>
|
||||
/// <value>The style preset.</value>
|
||||
[Category("LoadingCircle"),
|
||||
Description("Quickly sets the style to one of these presets, or a custom style if desired"),
|
||||
DefaultValue(typeof(StylePresets), "Custom")]
|
||||
public StylePresets StylePreset
|
||||
{
|
||||
get => _stylePreset;
|
||||
set
|
||||
{
|
||||
_stylePreset = value;
|
||||
|
||||
switch (_stylePreset)
|
||||
{
|
||||
case StylePresets.MacOsx:
|
||||
SetCircleAppearance(MacOsxNumberOfSpoke,
|
||||
MacOsxSpokeThickness, MacOsxInnerCircleRadius,
|
||||
MacOsxOuterCircleRadius);
|
||||
break;
|
||||
case StylePresets.Firefox:
|
||||
SetCircleAppearance(FireFoxNumberOfSpoke,
|
||||
FireFoxSpokeThickness, FireFoxInnerCircleRadius,
|
||||
FireFoxOuterCircleRadius);
|
||||
break;
|
||||
case StylePresets.Ie7:
|
||||
SetCircleAppearance(Ie7NumberOfSpoke,
|
||||
Ie7SpokeThickness, Ie7InnerCircleRadius,
|
||||
Ie7OuterCircleRadius);
|
||||
break;
|
||||
case StylePresets.Custom:
|
||||
SetCircleAppearance(DefaultNumberOfSpoke,
|
||||
DefaultSpokeThickness,
|
||||
DefaultInnerCircleRadius,
|
||||
DefaultOuterCircleRadius);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int _xxx = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Construtor Initializes a new instance of the <see cref="T:LoadingCircle"/> class.
|
||||
/// </summary>
|
||||
public BusyWaitAnimation()
|
||||
{
|
||||
_xxx++;
|
||||
|
||||
_color = _defaultColor;
|
||||
BgColor = Color.LightGray;
|
||||
|
||||
GenerateColorsPallet();
|
||||
GetSpokesAngles();
|
||||
GetControlCenterPoint();
|
||||
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Tick += new EventHandler(aTimer_Tick);
|
||||
ActiveTimer();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Tick event of the aTimer control.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event.</param>
|
||||
/// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
|
||||
void aTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
_progressValue = ++_progressValue % _numberOfSpoke;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Raises the <see cref="E:System.Windows.Forms.Control.Paint"></see> event.
|
||||
/// </summary>
|
||||
/// <param name="g">A <see cref="T:System.Windows.Forms.PaintEventArgs"></see> that contains the event data.</param>
|
||||
private void OnPaint(Graphics g)
|
||||
{
|
||||
if (_numberOfSpoke <= 0) return;
|
||||
_backgroundGraphics.Clear(BgColor);
|
||||
|
||||
g.SmoothingMode = SmoothingMode.HighQuality;
|
||||
|
||||
var intPosition = _progressValue;
|
||||
for (var intCounter = 0; intCounter < _numberOfSpoke; intCounter++)
|
||||
{
|
||||
intPosition = intPosition % _numberOfSpoke;
|
||||
DrawLine(_backgroundGraphics,
|
||||
GetCoordinate(_centerPoint, _innerCircleRadius, _angles[intPosition]),
|
||||
GetCoordinate(_centerPoint, _outerCircleRadius, _angles[intPosition]),
|
||||
_colors[intCounter], _spokeThickness);
|
||||
intPosition++;
|
||||
}
|
||||
|
||||
g.DrawImage(_backgroundImage, new Point(_drawArea.Left, _drawArea.Top));
|
||||
}
|
||||
|
||||
|
||||
// Methods ===========================================================
|
||||
/// <summary>
|
||||
/// Darkens a specified color.
|
||||
/// </summary>
|
||||
/// <param name="objColor">Color to darken.</param>
|
||||
/// <param name="intPercent">The percent of darken.</param>
|
||||
/// <returns>The new color generated.</returns>
|
||||
private Color Darken(Color objColor, int intPercent)
|
||||
{
|
||||
int intRed = objColor.R;
|
||||
int intGreen = objColor.G;
|
||||
int intBlue = objColor.B;
|
||||
return Color.FromArgb(intPercent, Math.Min(intRed, byte.MaxValue), Math.Min(intGreen, byte.MaxValue), Math.Min(intBlue, byte.MaxValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the colors pallet.
|
||||
/// </summary>
|
||||
private void GenerateColorsPallet()
|
||||
{
|
||||
_colors = GenerateColorsPallet(_color, Active, _numberOfSpoke);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the colors pallet.
|
||||
/// </summary>
|
||||
/// <param name="color">Color of the lightest spoke.</param>
|
||||
/// <param name="bhadeColor">if set to <c>true</c> the color will be shaded on X spoke.</param>
|
||||
/// <param name="nbSpoke"></param>
|
||||
/// <returns>An array of color used to draw the circle.</returns>
|
||||
private Color[] GenerateColorsPallet(Color color, bool bhadeColor, int nbSpoke)
|
||||
{
|
||||
var objColors = new Color[NumberSpoke];
|
||||
|
||||
// Value is used to simulate a gradient feel... For each spoke, the
|
||||
// color will be darken by value in intIncrement.
|
||||
var bytIncrement = (byte)(byte.MaxValue / NumberSpoke);
|
||||
|
||||
//Reset variable in case of multiple passes
|
||||
byte percentageOfDarken = 0;
|
||||
|
||||
for (var intCursor = 0; intCursor < NumberSpoke; intCursor++)
|
||||
{
|
||||
if (bhadeColor)
|
||||
{
|
||||
if (intCursor == 0 || intCursor < NumberSpoke - nbSpoke)
|
||||
objColors[intCursor] = color;
|
||||
else
|
||||
{
|
||||
// Increment alpha channel color
|
||||
percentageOfDarken += bytIncrement;
|
||||
|
||||
// Ensure that we don't exceed the maximum alpha
|
||||
// channel value (255)
|
||||
if (percentageOfDarken > byte.MaxValue)
|
||||
percentageOfDarken = byte.MaxValue;
|
||||
|
||||
// Determine the spoke forecolor
|
||||
objColors[intCursor] = Darken(color, percentageOfDarken);
|
||||
}
|
||||
}
|
||||
else
|
||||
objColors[intCursor] = color;
|
||||
}
|
||||
|
||||
return objColors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the control center point.
|
||||
/// </summary>
|
||||
private void GetControlCenterPoint()
|
||||
{
|
||||
// m_CenterPoint = GetControlCenterPoint(this);
|
||||
_centerPoint = new PointF(_drawArea.Width / 2, _drawArea.Height / 2 - 1);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Draws the line with GDI+.
|
||||
/// </summary>
|
||||
/// <param name="graphics">The Graphics object.</param>
|
||||
/// <param name="pointOne">The point one.</param>
|
||||
/// <param name="pointTwo">The point two.</param>
|
||||
/// <param name="color">Color of the spoke.</param>
|
||||
/// <param name="lineThickness">The thickness of spoke.</param>
|
||||
private void DrawLine(Graphics graphics, PointF pointOne, PointF pointTwo,
|
||||
Color color, int lineThickness)
|
||||
{
|
||||
using (var objPen = new Pen(new SolidBrush(color), lineThickness))
|
||||
{
|
||||
objPen.StartCap = LineCap.Round;
|
||||
objPen.EndCap = LineCap.Round;
|
||||
graphics.DrawLine(objPen, pointOne, pointTwo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the coordinate.
|
||||
/// </summary>
|
||||
/// <param name="circleCenter">The Circle center.</param>
|
||||
/// <param name="radius">The radius.</param>
|
||||
/// <param name="angle">The angle.</param>
|
||||
/// <returns></returns>
|
||||
private PointF GetCoordinate(PointF circleCenter, int radius, double angle)
|
||||
{
|
||||
var dblAngle = Math.PI * angle / NumberOfDegreesInHalfCircle;
|
||||
|
||||
return new PointF(circleCenter.X + radius * (float)Math.Cos(dblAngle),
|
||||
circleCenter.Y + radius * (float)Math.Sin(dblAngle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spokes angles.
|
||||
/// </summary>
|
||||
private void GetSpokesAngles()
|
||||
{
|
||||
_angles = GetSpokesAngles(NumberSpoke);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spoke angles.
|
||||
/// </summary>
|
||||
/// <param name="numberSpoke">The number spoke.</param>
|
||||
/// <returns>An array of angle.</returns>
|
||||
private double[] GetSpokesAngles(int numberSpoke)
|
||||
{
|
||||
var angles = new double[numberSpoke];
|
||||
var angle = NumberOfDegreesInCircle / numberSpoke;
|
||||
|
||||
for (var shtCounter = 0; shtCounter < numberSpoke; shtCounter++)
|
||||
angles[shtCounter] = (shtCounter == 0 ? angle : angles[shtCounter - 1] + angle);
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actives the timer.
|
||||
/// </summary>
|
||||
private void ActiveTimer()
|
||||
{
|
||||
if (_isTimerActive)
|
||||
_timer.Start();
|
||||
else
|
||||
{
|
||||
_timer.Stop();
|
||||
_progressValue = 0;
|
||||
}
|
||||
|
||||
GenerateColorsPallet();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the circle appearance.
|
||||
/// </summary>
|
||||
/// <param name="numberSpoke">The number spoke.</param>
|
||||
/// <param name="spokeThickness">The spoke thickness.</param>
|
||||
/// <param name="innerCircleRadius">The inner circle radius.</param>
|
||||
/// <param name="outerCircleRadius">The outer circle radius.</param>
|
||||
public void SetCircleAppearance(int numberSpoke, int spokeThickness, int innerCircleRadius, int outerCircleRadius)
|
||||
{
|
||||
NumberSpoke = numberSpoke;
|
||||
SpokeThickness = spokeThickness;
|
||||
InnerCircleRadius = innerCircleRadius;
|
||||
OuterCircleRadius = outerCircleRadius;
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
#region new methods
|
||||
|
||||
private Rectangle _drawArea;
|
||||
private Graphics _graphics;
|
||||
private int _hDc = int.MinValue;
|
||||
private Bitmap _backgroundImage;
|
||||
private Graphics _backgroundGraphics;
|
||||
|
||||
private void Invalidate()
|
||||
{
|
||||
if (_graphics != null)
|
||||
{
|
||||
_graphics.DrawImageUnscaled(_backgroundImage, new Point(_drawArea.Left, _drawArea.Top));
|
||||
OnPaint(_graphics);
|
||||
}
|
||||
}
|
||||
|
||||
public Graphics GDIGraphics => _graphics;
|
||||
|
||||
public int hDC
|
||||
{
|
||||
set
|
||||
{
|
||||
|
||||
if (_graphics != null) _graphics.Dispose();
|
||||
_hDc = value;
|
||||
_graphics = Graphics.FromHdc(new IntPtr(value));
|
||||
|
||||
}
|
||||
|
||||
get => _hDc;
|
||||
}
|
||||
|
||||
public void SetDrawArea(Rectangle rect)
|
||||
{
|
||||
_drawArea = new Rectangle(rect.X, rect.Y, rect.Width, rect.Height);
|
||||
GetControlCenterPoint();
|
||||
|
||||
_backgroundImage = new Bitmap(rect.Width, rect.Height);
|
||||
|
||||
_backgroundGraphics = Graphics.FromImage(_backgroundImage);
|
||||
_backgroundGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
171
Common/DTS.Common/Utils/ByteConverter.cs
Normal file
171
Common/DTS.Common/Utils/ByteConverter.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
// Converts integral types to bytes but wraps the conversions so that
|
||||
// outgoing they are converted to network order and incoming they are
|
||||
// converted to host order.
|
||||
public class ByteConvertor
|
||||
{
|
||||
public static void Convert(byte[] input, int offset, out byte value)
|
||||
{
|
||||
// Nothing to do for single bytes
|
||||
value = input[offset];
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out ushort value)
|
||||
{
|
||||
value = (ushort)(0 |
|
||||
input[offset + 0] << 8 |
|
||||
input[offset + 1] << 0);
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out short value)
|
||||
{
|
||||
value = (short)(0 |
|
||||
input[offset + 0] << 8 |
|
||||
input[offset + 1] << 0);
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out uint value)
|
||||
{
|
||||
value = 0 |
|
||||
(uint)input[offset + 0] << 24 |
|
||||
(uint)input[offset + 1] << 16 |
|
||||
(uint)input[offset + 2] << 8 |
|
||||
(uint)input[offset + 3] << 0;
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out int value)
|
||||
{
|
||||
value =
|
||||
input[offset + 0] << 24 |
|
||||
input[offset + 1] << 16 |
|
||||
input[offset + 2] << 8 |
|
||||
input[offset + 3] << 0;
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out ulong value)
|
||||
{
|
||||
value = 0 |
|
||||
(ulong)input[offset + 0] << 56 |
|
||||
(ulong)input[offset + 1] << 48 |
|
||||
(ulong)input[offset + 2] << 40 |
|
||||
(ulong)input[offset + 3] << 32 |
|
||||
(ulong)input[offset + 4] << 24 |
|
||||
(ulong)input[offset + 5] << 16 |
|
||||
(ulong)input[offset + 6] << 8 |
|
||||
(ulong)input[offset + 7] << 0;
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out long value)
|
||||
{
|
||||
value = 0 |
|
||||
(long)input[offset + 0] << 56 |
|
||||
(long)input[offset + 1] << 48 |
|
||||
(long)input[offset + 2] << 40 |
|
||||
(long)input[offset + 3] << 32 |
|
||||
(long)input[offset + 4] << 24 |
|
||||
(long)input[offset + 5] << 16 |
|
||||
(long)input[offset + 6] << 8 |
|
||||
(long)input[offset + 7] << 0;
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out float value)
|
||||
{
|
||||
value = BitConverter.ToSingle(input, offset);
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out double value)
|
||||
{
|
||||
value = BitConverter.ToDouble(input, offset);
|
||||
}
|
||||
|
||||
public static void Convert(byte[] input, int offset, out string value)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (var i = offset; i < input.Length && input[i] != 0; i++)
|
||||
{
|
||||
sb.Append((char)input[i]);
|
||||
}
|
||||
value = sb.ToString();
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(string input)
|
||||
{
|
||||
var c = input.ToCharArray();
|
||||
var rv = new byte[c.Length + 1];
|
||||
for (var i = 0; i < c.Length; i++) rv[i] = (byte)c[i];
|
||||
rv[c.Length] = 0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(byte input)
|
||||
{
|
||||
var rv = new byte[1];
|
||||
rv[0] = input;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(ushort input)
|
||||
{
|
||||
var rv = new byte[2];
|
||||
rv[0] = (byte)((input >> 8) & 0xFF);
|
||||
rv[1] = (byte)((input >> 0) & 0xFF);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(short input)
|
||||
{
|
||||
var rv = new byte[2];
|
||||
rv[0] = (byte)((input >> 8) & 0xFF);
|
||||
rv[1] = (byte)((input >> 0) & 0xFF);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(uint input)
|
||||
{
|
||||
var rv = new byte[4];
|
||||
rv[0] = (byte)((input >> 24) & 0xFF);
|
||||
rv[1] = (byte)((input >> 16) & 0xFF);
|
||||
rv[2] = (byte)((input >> 8) & 0xFF);
|
||||
rv[3] = (byte)((input >> 0) & 0xFF);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(int input)
|
||||
{
|
||||
var rv = new byte[4];
|
||||
rv[0] = (byte)((input >> 24) & 0xFF);
|
||||
rv[1] = (byte)((input >> 16) & 0xFF);
|
||||
rv[2] = (byte)((input >> 8) & 0xFF);
|
||||
rv[3] = (byte)((input >> 0) & 0xFF);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(ulong input)
|
||||
{
|
||||
var rv = new byte[8];
|
||||
rv[0] = (byte)((input >> 56) & 0xFF);
|
||||
rv[1] = (byte)((input >> 48) & 0xFF);
|
||||
rv[2] = (byte)((input >> 40) & 0xFF);
|
||||
rv[3] = (byte)((input >> 32) & 0xFF);
|
||||
rv[4] = (byte)((input >> 24) & 0xFF);
|
||||
rv[5] = (byte)((input >> 16) & 0xFF);
|
||||
rv[6] = (byte)((input >> 8) & 0xFF);
|
||||
rv[7] = (byte)((input >> 0) & 0xFF);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(float input)
|
||||
{
|
||||
return BitConverter.GetBytes(input);
|
||||
}
|
||||
|
||||
public static byte[] ToByteArray(double input)
|
||||
{
|
||||
return BitConverter.GetBytes(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
494
Common/DTS.Common/Utils/Database.cs
Normal file
494
Common/DTS.Common/Utils/Database.cs
Normal file
@@ -0,0 +1,494 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public static class Database
|
||||
{
|
||||
#region properties
|
||||
/// <summary>
|
||||
/// used for output from processes
|
||||
/// </summary>
|
||||
private static StringBuilder sb = new StringBuilder();
|
||||
private static StringBuilder sbError = new StringBuilder();
|
||||
/// <summary>
|
||||
/// lock to prevent multiple threads operating on sb simultaneously
|
||||
/// </summary>
|
||||
private static readonly object PROCESS_LOCK = new object();
|
||||
#endregion properties
|
||||
#region methods
|
||||
/// <summary>
|
||||
/// used to collect output from a running process
|
||||
/// </summary>
|
||||
/// <param name="sendingProcess"></param>
|
||||
/// <param name="outLine"></param>
|
||||
private static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
|
||||
{
|
||||
if (outLine.Data != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(outLine.Data))
|
||||
{
|
||||
sb.Append("\r\n");
|
||||
}
|
||||
|
||||
sb.Append(outLine.Data);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// log function optionally passed into functions
|
||||
/// </summary>
|
||||
/// <param name="paramlist"></param>
|
||||
public delegate void LogDelegate(params object[] paramlist);
|
||||
|
||||
/// <summary>
|
||||
/// starts a process with a given command and logs
|
||||
/// </summary>
|
||||
/// <param name="sqlLocalDbExeFileName"></param>
|
||||
/// <param name="command"></param>
|
||||
/// <param name="log"></param>
|
||||
/// <returns></returns>
|
||||
private static string SqlCommandProcessor(string sqlLocalDbExeFileName, string command, LogDelegate log)
|
||||
{
|
||||
var resultString = string.Empty;
|
||||
lock (PROCESS_LOCK)
|
||||
{
|
||||
sb.Clear();
|
||||
sbError.Clear();
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = sqlLocalDbExeFileName,
|
||||
Arguments = command,
|
||||
LoadUserProfile = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
//* Set ONLY ONE handler here.
|
||||
process.OutputDataReceived += OutputHandler;
|
||||
process.ErrorDataReceived += Process_ErrorDataReceived;
|
||||
//* Start process
|
||||
process.Start();
|
||||
//* Read one element asynchronously
|
||||
process.BeginErrorReadLine();
|
||||
//* Read the other one synchronously
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
Console.WriteLine(output);
|
||||
log?.Invoke($"Result of {command} command is: {output}");
|
||||
|
||||
process.WaitForExit();
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
resultString = sb.ToString();
|
||||
}
|
||||
if (sbError.Length > 0)
|
||||
{
|
||||
log?.Invoke($"Error command: {command} error: {sbError}");
|
||||
resultString += sbError.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return resultString;
|
||||
}
|
||||
/// <summary>
|
||||
/// handles any error output from running a SqlCmd.exe process
|
||||
/// </summary>
|
||||
private static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.Data))
|
||||
{
|
||||
sbError.Append("\r\n");
|
||||
}
|
||||
sbError.Append(e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// gets the path to SqlServer
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetSqlServerLocalDbPath()
|
||||
{
|
||||
var highestVersionInstalledPath = string.Empty;
|
||||
|
||||
var rk = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
|
||||
var sk1 = rk.OpenSubKey("SOFTWARE\\Microsoft\\Microsoft SQL Server Local DB\\Installed Versions");
|
||||
if (sk1 == null) return string.Empty;
|
||||
var maxProductVersion = 0.0;
|
||||
foreach (var productSubKeyName in sk1.GetSubKeyNames())
|
||||
{
|
||||
if (!double.TryParse(productSubKeyName, NumberStyles.Float, CultureInfo.InvariantCulture, out var thisVersion)) continue;
|
||||
if (thisVersion < maxProductVersion) continue;
|
||||
maxProductVersion = thisVersion;
|
||||
var newKey = sk1.OpenSubKey(productSubKeyName);
|
||||
if (newKey == null) continue;
|
||||
var val = newKey.GetValue("InstanceAPIPath", -1, RegistryValueOptions.None).ToString();
|
||||
if (val == "-1" || !val.EndsWith("SqlUserInstance.dll")) continue;
|
||||
highestVersionInstalledPath = val.Substring(0, val.Length - "SqlUserInstance.dll".Length);
|
||||
}
|
||||
|
||||
var envProgFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
Utilities.Logging.APILogger.Log($"Environment.Program Files is {envProgFiles}");
|
||||
var envProgFilesNoDrive = envProgFiles.Substring(envProgFiles.LastIndexOf("\\") + 1);
|
||||
Utilities.Logging.APILogger.Log($"envProgFilesNoDrive is {envProgFilesNoDrive}");
|
||||
Utilities.Logging.APILogger.Log($"highestVersionInstalledPath before Replace is {highestVersionInstalledPath}");
|
||||
highestVersionInstalledPath = highestVersionInstalledPath.Replace("Program Files", envProgFilesNoDrive);
|
||||
Utilities.Logging.APILogger.Log($"highestVersionInstalledPath after Replace is {highestVersionInstalledPath}");
|
||||
|
||||
return highestVersionInstalledPath.Replace("LocalDB", "Tools");
|
||||
}
|
||||
/// <summary>
|
||||
/// Instead of relying on the Path environment variable to have the path to the SQLCMD.EXE that
|
||||
/// we need to run, earlier in the Path list than a path to an older version of SQLCMD.EXE that
|
||||
/// will not work with the SqlLocalDb version that we are using, search the registry for the
|
||||
/// newest version of SQLCMD.exe and return that path.
|
||||
/// The path should be something like $"C:\\Program Files\\Microsoft SQL Server\\Client SDK\\ODBC\\110\\Tools\\Binn"
|
||||
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetODBCToolsPath(LogDelegate log)
|
||||
{
|
||||
var ODBCToolsPath = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var rk = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
|
||||
var sk1 = rk.OpenSubKey("SOFTWARE\\Microsoft\\Microsoft SQL Server");
|
||||
if (sk1 == null) return string.Empty;
|
||||
ODBCToolsPath = ScanRegistry(log, sk1, true);
|
||||
if (string.IsNullOrWhiteSpace(ODBCToolsPath))
|
||||
{
|
||||
//We didn't find the patch using the previous method (ensuring that it had a CurrentVersion sub folder)
|
||||
//so try to find the path without that sub folder.
|
||||
ODBCToolsPath = ScanRegistry(log, sk1, false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Invoke(ex.Message);
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
log?.Invoke($"ODBCToolsPath is {ODBCToolsPath}");
|
||||
}
|
||||
|
||||
return ODBCToolsPath;
|
||||
// This should return something like $"C:\\Program Files\\Microsoft SQL Server\\Client SDK\\ODBC\\110\\Tools\\Binn";
|
||||
}
|
||||
private static string ScanRegistry(LogDelegate log, RegistryKey sk1, bool checkCurrentVersion)
|
||||
{
|
||||
var ODBCToolsPath = string.Empty;
|
||||
|
||||
var envProgFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
||||
Utilities.Logging.APILogger.Log($"Environment.Program Files is {envProgFiles}");
|
||||
var envProgFilesNoDrive = envProgFiles.Substring(envProgFiles.LastIndexOf("\\") + 1);
|
||||
Utilities.Logging.APILogger.Log($"envProgFilesNoDrive is {envProgFilesNoDrive}");
|
||||
|
||||
try
|
||||
{
|
||||
var maxProductVersion = 0;
|
||||
var maxProductSubKeyNameInt = 0;
|
||||
foreach (var productSubKeyName in sk1.GetSubKeyNames())
|
||||
{
|
||||
if (!double.TryParse(productSubKeyName, NumberStyles.Float, CultureInfo.InvariantCulture, out var thisVersion)) continue;
|
||||
var productSubKey = sk1.OpenSubKey(productSubKeyName);
|
||||
if (productSubKey == null) continue;
|
||||
foreach (var folderName in productSubKey.GetSubKeyNames())
|
||||
{
|
||||
if (folderName == "Tools")
|
||||
{
|
||||
//e.g. Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\120\Tools
|
||||
var toolsFolderSubkey = productSubKey.OpenSubKey(folderName);
|
||||
if (toolsFolderSubkey == null) continue;
|
||||
foreach (var toolsSubFolderName in toolsFolderSubkey.GetSubKeyNames())
|
||||
{
|
||||
if (toolsSubFolderName == "ClientSetup")
|
||||
{
|
||||
//e.g. Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\120\Tools\ClientSetup
|
||||
var clientSetupFolderSubkey = toolsFolderSubkey.OpenSubKey(toolsSubFolderName);
|
||||
if (clientSetupFolderSubkey == null) continue;
|
||||
var pathValue = clientSetupFolderSubkey.GetValue("ODBCToolsPath", -1, RegistryValueOptions.None).ToString();
|
||||
if (pathValue == "-1")
|
||||
{
|
||||
log?.Invoke($"There is no ODBCToolsPath subkey in {clientSetupFolderSubkey}");
|
||||
continue;
|
||||
}
|
||||
Utilities.Logging.APILogger.Log($"pathValue before Replace is {pathValue}");
|
||||
pathValue = pathValue.Replace("Program Files", envProgFilesNoDrive);
|
||||
Utilities.Logging.APILogger.Log($"pathValue after Replace is {pathValue}");
|
||||
|
||||
var sqlcmdFile = Path.Combine(pathValue, "SQLCMD.EXE");
|
||||
log?.Invoke($"Looking for {sqlcmdFile}");
|
||||
if (!File.Exists(sqlcmdFile))
|
||||
{
|
||||
log?.Invoke($"No file named {sqlcmdFile} exists");
|
||||
//Try without the "\Client SDK\ODBC\" folders
|
||||
pathValue = pathValue.Replace("Client SDK\\ODBC\\", "");
|
||||
sqlcmdFile = Path.Combine(pathValue, "SQLCMD.EXE");
|
||||
log?.Invoke($"Now looking for {sqlcmdFile}");
|
||||
if (!File.Exists(sqlcmdFile)) continue;
|
||||
log?.Invoke($"Found {sqlcmdFile}");
|
||||
}
|
||||
if (checkCurrentVersion)
|
||||
{
|
||||
foreach (var clientSetupSubFolderName in clientSetupFolderSubkey.GetSubKeyNames())
|
||||
{
|
||||
if (clientSetupSubFolderName == "CurrentVersion")
|
||||
{
|
||||
//Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server\120\Tools\ClientSetup\CurrentVersion
|
||||
var currentVersionFolderSubkey = clientSetupFolderSubkey.OpenSubKey(clientSetupSubFolderName);
|
||||
if (currentVersionFolderSubkey == null) continue;
|
||||
var versionValue = currentVersionFolderSubkey.GetValue("CurrentVersion", -1, RegistryValueOptions.None).ToString();
|
||||
if (versionValue == "-1") continue;
|
||||
var majorVersionString = versionValue.Split('.')[0];
|
||||
if (!Int32.TryParse(majorVersionString, NumberStyles.Integer, CultureInfo.InvariantCulture, out var majorVersionInt)) continue;
|
||||
if (majorVersionInt > maxProductVersion)
|
||||
{
|
||||
maxProductVersion = majorVersionInt;
|
||||
ODBCToolsPath = pathValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Int32.TryParse(productSubKeyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var productSubKeyNameInt)) continue;
|
||||
if (productSubKeyNameInt > maxProductSubKeyNameInt)
|
||||
{
|
||||
maxProductSubKeyNameInt = productSubKeyNameInt;
|
||||
ODBCToolsPath = pathValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log?.Invoke(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
log?.Invoke($"ODBCToolsPath is {ODBCToolsPath}");
|
||||
}
|
||||
|
||||
return ODBCToolsPath;
|
||||
}
|
||||
/// <summary>
|
||||
/// installed, and run the command passed in.
|
||||
/// </summary>
|
||||
/// Get the path to the latest version of SQL Server Express LocalDB
|
||||
/// <param name="command"></param>
|
||||
/// <returns></returns>
|
||||
private static string ProcessSqlLocalDbCommand(string command, LogDelegate log)
|
||||
{
|
||||
//SQL Server Express LocalDB 2014 is a Prerequisite of the DataPRO Installer,
|
||||
//so it should be there unless it has been subsequently uninstalled.
|
||||
var localDbPath = GetSqlServerLocalDbPath();
|
||||
if (localDbPath == string.Empty)
|
||||
{
|
||||
//SQL Server LocalDb is not installed so display error and go away
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.LocalDbDoesntExist);
|
||||
}
|
||||
var sqlLocalDbExeFileName = localDbPath + "SqlLocalDB.exe";
|
||||
return SqlCommandProcessor(sqlLocalDbExeFileName, command, log);
|
||||
}
|
||||
private static string BatchCommandProcessor(string batchFileName,
|
||||
string dbName,
|
||||
string sqlDbFileName,
|
||||
string sqlLogFileName,
|
||||
string fullSqlcmdPath,
|
||||
LogDelegate log)
|
||||
{
|
||||
var resultString = string.Empty;
|
||||
lock (PROCESS_LOCK)
|
||||
{
|
||||
sb.Clear();
|
||||
sbError.Clear();
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo =
|
||||
{
|
||||
FileName = batchFileName,
|
||||
Arguments = dbName + " " + "\"" + sqlDbFileName + "\"" + " " + "\"" + sqlLogFileName + "\"" + " " + "\"" + fullSqlcmdPath + "\"",
|
||||
LoadUserProfile = true,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
//* Set ONLY ONE handler here.
|
||||
process.OutputDataReceived += OutputHandler;
|
||||
process.ErrorDataReceived += Process_ErrorDataReceived;
|
||||
//* Start process
|
||||
process.Start();
|
||||
//* Read one element asynchronously
|
||||
process.BeginErrorReadLine();
|
||||
//* Read the other one synchronously
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
Console.WriteLine(output);
|
||||
log?.Invoke($"Result of attach {dbName} using {sqlDbFileName} and {sqlLogFileName} is:");
|
||||
log?.Invoke(output);
|
||||
process.WaitForExit();
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
resultString = sb.ToString();
|
||||
}
|
||||
if (sbError.Length > 0)
|
||||
{
|
||||
resultString += sbError.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return resultString;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// attaches to a given database
|
||||
/// throws DbNotAttached exception
|
||||
/// </summary>
|
||||
/// <param name="targetDir"></param>
|
||||
/// <param name="dbName"></param>
|
||||
/// <param name="dbFolder"></param>
|
||||
/// <param name="scriptsFolder"></param>
|
||||
/// <param name="attachDBsbat"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void AttachDatabase(string targetDir,
|
||||
string dbName,
|
||||
string dbFolder,
|
||||
string scriptsFolder,//StringResources.ScriptsFolder
|
||||
string attachDBsbat,
|
||||
LogDelegate log) //StringResources.AttachDBsbat
|
||||
{
|
||||
const string SqlCmdExe = "sqlcmd.exe";
|
||||
|
||||
var dbFileName = Path.Combine(Environment.CurrentDirectory, dbFolder, dbName) + ".mdf";
|
||||
var logFileName = Path.Combine(Environment.CurrentDirectory, dbFolder, dbName) + "_log.ldf";
|
||||
var batchFileName = Path.Combine(targetDir, scriptsFolder, attachDBsbat);
|
||||
var oDBCToolsPath = DTS.Common.Utils.Database.GetODBCToolsPath(log);
|
||||
var fullSqlcmdPath = Path.Combine(oDBCToolsPath, SqlCmdExe); //e.g. $"\"C:\\Program Files\\Microsoft SQL Server\\Client SDK\\ODBC\\110\\Tools\\Binn\\sqlcmd.exe\""
|
||||
var resultString = BatchCommandProcessor(batchFileName, dbName, dbFileName, logFileName, fullSqlcmdPath, log);
|
||||
if (resultString.Length != 0)
|
||||
{
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.DbNotAttached);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// checks to see if database files exist
|
||||
/// throws FileNotFound exception
|
||||
/// </summary>
|
||||
/// <param name="defaultDbName"></param>
|
||||
/// <param name="dbFolder"></param>
|
||||
public static void CheckLocalDatabaseFilesExist(string defaultDbName, string dbFolder)
|
||||
{
|
||||
var dbFileNameSource = Path.Combine(Environment.CurrentDirectory, dbFolder, defaultDbName) + ".mdf";
|
||||
var logFileNameSource = Path.Combine(Environment.CurrentDirectory, dbFolder, defaultDbName) + "_log.ldf";
|
||||
if (!File.Exists(dbFileNameSource))
|
||||
{
|
||||
throw new FileNotFoundException(dbFileNameSource);
|
||||
}
|
||||
if (!File.Exists(logFileNameSource))
|
||||
{
|
||||
throw new FileNotFoundException(logFileNameSource);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// creates an SQL local db instance
|
||||
/// throws FailedToCreateInstance exception
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void CreateInstance(string instance, LogDelegate log)
|
||||
{
|
||||
var resultString = ProcessSqlLocalDbCommand($"create {instance}", log);
|
||||
if (resultString.Length != 0)
|
||||
{
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.FailedToCreateInstance);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// stops a given sql local db instance
|
||||
/// throws LocalDbDoesntExist exception
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void StopInstance(string instance, LogDelegate log)
|
||||
{
|
||||
var resultString = ProcessSqlLocalDbCommand($"stop {instance}", log);
|
||||
if (resultString.Length != 0)
|
||||
{
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.FailedToStopInstance, resultString);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// deletes a given sql local db instance
|
||||
/// throws FailedToDeleteInstance exception
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void DeleteInstance(string instance, LogDelegate log)
|
||||
{
|
||||
var resultString = ProcessSqlLocalDbCommand($"delete {instance}", log);
|
||||
if (resultString.Length != 0)
|
||||
{
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.FailedToDeleteInstance);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// starts a local db instance
|
||||
/// throws FailedToStartInstance exception
|
||||
/// </summary>
|
||||
/// <param name="instance"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void StartInstance(string instance, LogDelegate log)
|
||||
{
|
||||
var resultString = ProcessSqlLocalDbCommand($"start {instance}", log);
|
||||
if (resultString.Length != 0)
|
||||
{
|
||||
throw new SqlServerLocalDbException(SqlServerLocalDbException.Errors.FailedToStartInstance);
|
||||
}
|
||||
}
|
||||
#endregion methods
|
||||
public class SqlServerLocalDbException
|
||||
: Exception
|
||||
{
|
||||
public Errors Error { get; }
|
||||
|
||||
public SqlServerLocalDbException(Errors error)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
public SqlServerLocalDbException(Errors error, string msg)
|
||||
: base(msg)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
public enum Errors
|
||||
{
|
||||
FailedToStopInstance,
|
||||
LocalDbDoesntExist,//sql local db doesn't exist
|
||||
FailedToDeleteInstance,
|
||||
FailedToCreateInstance,
|
||||
FailedToStartInstance,
|
||||
DbNotAttached,
|
||||
IsodbNotAttached,
|
||||
DASFactoryDBNotAttached,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
24
Common/DTS.Common/Utils/EnumUtils.cs
Normal file
24
Common/DTS.Common/Utils/EnumUtils.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public static class EnumUtil
|
||||
{
|
||||
public static IEnumerable<T> GetValues<T>()
|
||||
{
|
||||
return Enum.GetValues(typeof(T)).Cast<T>();
|
||||
}
|
||||
public static ItemCollection GetValuesList<T>()
|
||||
{
|
||||
var list = new ItemCollection();
|
||||
foreach (var filter in Enum.GetValues(typeof(T)))
|
||||
{
|
||||
list.Add(filter, Enum.GetName(typeof(T), filter));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
462
Common/DTS.Common/Utils/FileUtils.cs
Normal file
462
Common/DTS.Common/Utils/FileUtils.cs
Normal file
@@ -0,0 +1,462 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
|
||||
// ReSharper disable UnusedMember.Local
|
||||
// ReSharper disable UnusedVariable
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public static class FileUtils
|
||||
{
|
||||
[Flags]
|
||||
enum MoveFileFlags
|
||||
{
|
||||
MOVEFILE_REPLACE_EXISTING = 0x00000001,
|
||||
MOVEFILE_COPY_ALLOWED = 0x00000002,
|
||||
MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004,
|
||||
MOVEFILE_WRITE_THROUGH = 0x00000008,
|
||||
MOVEFILE_CREATE_HARDLINK = 0x00000010,
|
||||
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DeleteFile(string lpFileName);
|
||||
public const double DataPROPre20XmlVersion = 2.0D;
|
||||
public const double DataPRO20XmlVersion = 3.0D;
|
||||
public const double DataPRO21XmlVersion = 4.0D;
|
||||
public const double DataPRO22XmlVersion = 5.0D;
|
||||
//FB 13120 Added FilterClass and deleted CFC in version 6.0
|
||||
//15390 Store the ZMO in EU in the DataPRO DB in version 7.0, adds top level field SensorChangeHistory
|
||||
//13065 Sensor "First Use" Date, version 8, adds LatestCalibrationId, CalibrationId and FirstUse tags
|
||||
//15727Building and Replacing Racks/Mods - version 9, adds TestId,GroupId, StandIn to hardware and Id to tests
|
||||
//Version 10 adds TSR Air settings
|
||||
//Version 11 adds AlignUDPToPPS
|
||||
public const double CurrentXmlVersion = 11.0D;
|
||||
|
||||
|
||||
public static XmlElement GetImportXmlNode(string filename, string xmlDoc, out double importVersion)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
importVersion = 0D;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(xmlDoc))
|
||||
{
|
||||
doc.LoadXml(xmlDoc);
|
||||
}
|
||||
else
|
||||
{
|
||||
doc.Load(filename);
|
||||
}
|
||||
|
||||
foreach (var node in doc.ChildNodes)
|
||||
{
|
||||
if (node is XmlDeclaration) continue;
|
||||
if (!(node is XmlElement)) continue;
|
||||
if ((node as XmlElement).Name != "ExportFile") continue;
|
||||
|
||||
importVersion = Convert.ToDouble(((XmlElement)node).GetAttribute("Version"),
|
||||
System.Globalization.CultureInfo.InvariantCulture);
|
||||
if (importVersion > CurrentXmlVersion)
|
||||
{
|
||||
throw new NotSupportedException("Unsupported version: " + importVersion);
|
||||
}
|
||||
|
||||
return (XmlElement)node;
|
||||
}
|
||||
|
||||
throw new Exception("Invalid Import XML");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This API can accept a parameter “MOVEFILE_DELAY_UNTIL_REBOOT ” to remove the file only after reboot.
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240(v=vs.85).aspx
|
||||
/// </summary>
|
||||
/// <param name="filePath">Entire path of the file we want to move</param>
|
||||
public static void moveFileEx(string filePath)
|
||||
{
|
||||
MoveFileEx(filePath, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FB16400: ISO exports error in 3rd Party Software, related to FB15801
|
||||
/// By default, C# returns UTF-8 Encoding with Byte-Order Mark turned on. Need to explicitly call the overloaded constructor to turn it off:
|
||||
/// https://docs.microsoft.com/en-us/dotnet/api/system.text.encoding.utf8?view=netcore-3.1
|
||||
/// </summary>
|
||||
/// <param name="codepage"></param>
|
||||
/// <returns></returns>
|
||||
public static Encoding GetEncoding(int codepage)
|
||||
{
|
||||
var encoding = Encoding.GetEncoding(codepage);
|
||||
if (encoding is UTF8Encoding) { encoding = new UTF8Encoding(false); }
|
||||
return encoding;
|
||||
}
|
||||
|
||||
public static XmlWriter GetExportWriter(int count, double version, string software, string softwareVersion, LogDelegate logDelegate, out StringBuilder sb)
|
||||
{
|
||||
sb = new StringBuilder(5000000);
|
||||
|
||||
var xSet = new XmlWriterSettings { Indent = true, CheckCharacters = true };
|
||||
var writer = XmlWriter.Create(sb, xSet);
|
||||
|
||||
writer.WriteStartDocument();
|
||||
|
||||
writer.WriteStartElement("ExportFile");
|
||||
|
||||
writer.WriteAttributeString("TotalItems", count.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("Version", version.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
writer.WriteAttributeString("Software", software);
|
||||
writer.WriteAttributeString("SoftwareVersion", softwareVersion);
|
||||
return writer;
|
||||
}
|
||||
/// <summary>
|
||||
/// This API deletes an existing file.
|
||||
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363915(v=vs.85).aspx
|
||||
/// </summary>
|
||||
/// <param name="filePath">Entire path of the file to be deleted.</param>
|
||||
/// <param name="lastError">Error if failed to delete file
|
||||
/// If the function succeeds, the return value is nonzero.
|
||||
/// If the function fails, the return value is zero (0).
|
||||
/// To get extended error information, call GetLastError.</param>
|
||||
public static bool deleteFile(string filePath, ref int lastError)
|
||||
{
|
||||
if (!File.Exists(filePath)) return true;
|
||||
|
||||
var deleted = DeleteFile(filePath);
|
||||
if (!deleted)
|
||||
{
|
||||
lastError = Marshal.GetLastWin32Error();
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
public delegate void LogDelegate(params object[] paramlist);
|
||||
public static void DeleteFileOrMove(string filepath, LogDelegate logfunction)
|
||||
{
|
||||
var lastError = 0;
|
||||
if (!deleteFile(filepath, ref lastError))
|
||||
{
|
||||
logfunction("failed to delete file: ", filepath, " error: ", lastError);
|
||||
moveFileEx(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One time sql script conversion from SQLite syntax to MS SQL Server
|
||||
/// all you need to know about Regex is here - http://rubular.com/r/kcqDdLJBpx
|
||||
/// </summary>
|
||||
/// <param name="script">SQLite script file path</param>
|
||||
/// <returns></returns>
|
||||
public static string ScriptFromSQLiteToSQL(string script)
|
||||
{
|
||||
if (string.IsNullOrEmpty(script)) return script;
|
||||
|
||||
const string BLOB = "BLOB";
|
||||
const string NVCHAR = "NVCHAR";
|
||||
const string NVARCHAR = "NVARCHAR";
|
||||
const string COLLATE_NOCASE = "COLLATE NOCASE";
|
||||
const string Couldnt = "Couldn't";
|
||||
const string Couldnot = "Could not";
|
||||
const string IDENTITY = "IDENTITY(1,1)";
|
||||
const string AUTOINCREMENT = "AUTOINCREMENT";
|
||||
const string varbinary_max = "varbinary(max)";
|
||||
const string varchar_max = "varchar(max)";
|
||||
const string max = "(max)";
|
||||
const string BigInt = "bigint";
|
||||
const string Integer = "integer";
|
||||
const string Int = "int";
|
||||
const string _2048 = "(2048)";
|
||||
const string _5000 = "(5000)";
|
||||
|
||||
var sql = new StringBuilder();
|
||||
long count = 0;
|
||||
using (var inputStream = File.OpenRead(script))
|
||||
{
|
||||
using (var inputReader = new StreamReader(inputStream))
|
||||
{
|
||||
string tempLineValue;
|
||||
|
||||
while (null != (tempLineValue = inputReader.ReadLine()))
|
||||
{
|
||||
Debug.Print("Line: " + count++);
|
||||
var addGo = false;
|
||||
const string dropTable = "DROP TABLE IF EXISTS";
|
||||
const string dropTableNew = @"if exists(SELECT * FROM sysobjects where name = '{0}') DROP TABLE [dbo].[{0}];";
|
||||
const string insertInto = "INSERT INTO";
|
||||
const string convert = "convert(varbinary(max), {0})";
|
||||
|
||||
if (tempLineValue.StartsWith(dropTable))
|
||||
{
|
||||
var tableName = tempLineValue.Replace(dropTable, string.Empty).Replace("\"", string.Empty).Replace(";", string.Empty).Trim();
|
||||
tempLineValue = string.Format(dropTableNew, tableName);
|
||||
addGo = true;
|
||||
}
|
||||
else if (tempLineValue.StartsWith(insertInto))
|
||||
{
|
||||
|
||||
//find SQLite (or C#) date (Format: '2016-06-06 15:15:38.0630927' )
|
||||
const string patternSQLiteDate = @"(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2}).\d{3,7}";
|
||||
var regexSQLiteDate = new Regex(patternSQLiteDate, RegexOptions.IgnoreCase);
|
||||
var matchSQLiteDate = regexSQLiteDate.Match(tempLineValue);
|
||||
|
||||
while (matchSQLiteDate.Success)
|
||||
{
|
||||
var foundSQLiteDate = matchSQLiteDate.Value;
|
||||
|
||||
//find SQL date in SQLite date (Format: '2016-06-06 15:15:38.063' )
|
||||
const string patternSQLDate = @"(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})";
|
||||
var rDate = new Regex(patternSQLDate, RegexOptions.IgnoreCase);
|
||||
|
||||
var mDate = rDate.Match(foundSQLiteDate);
|
||||
if (mDate.Success)
|
||||
{
|
||||
//replace date
|
||||
tempLineValue = tempLineValue.Replace(foundSQLiteDate, mDate.Value);
|
||||
|
||||
}
|
||||
matchSQLiteDate = matchSQLiteDate.NextMatch();
|
||||
|
||||
}
|
||||
|
||||
const string patternBinary = @"[X]'\w+'";
|
||||
var regexBinary = new Regex(patternBinary, RegexOptions.IgnoreCase);
|
||||
var matchBinary = regexBinary.Match(tempLineValue);
|
||||
while (matchBinary.Success)
|
||||
{
|
||||
var matchValue = matchBinary.Value;
|
||||
var newValue = string.Format(convert, matchValue.Replace("X", string.Empty).Replace(",", string.Empty));
|
||||
tempLineValue = tempLineValue.Replace(matchValue, newValue);
|
||||
matchBinary = matchBinary.NextMatch();
|
||||
}
|
||||
addGo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const string patternText = @"text\s{0,}\(\w+\)";
|
||||
var regexText = new Regex(patternText, RegexOptions.IgnoreCase);
|
||||
var matchText = regexText.Match(tempLineValue);
|
||||
if (matchText.Success)
|
||||
{
|
||||
tempLineValue = tempLineValue.Replace(matchText.Value, varchar_max);
|
||||
}
|
||||
|
||||
const string patternMaxMemory = @"\[MaxMemory\]\sinteger,";
|
||||
var regexMaxMemory = new Regex(patternMaxMemory, RegexOptions.IgnoreCase);
|
||||
var matchMaxMemory = regexMaxMemory.Match(tempLineValue);
|
||||
if (matchMaxMemory.Success)
|
||||
{
|
||||
tempLineValue = tempLineValue.Replace(Integer, BigInt);
|
||||
}
|
||||
|
||||
}
|
||||
sql.AppendLine(tempLineValue);
|
||||
if (addGo) sql.AppendLine("GO");
|
||||
}
|
||||
}
|
||||
}
|
||||
return sql
|
||||
.Replace(AUTOINCREMENT, string.Empty)
|
||||
.Replace(Couldnt, Couldnot)
|
||||
.Replace(_2048, max)
|
||||
.Replace(_5000, max)
|
||||
.Replace(Integer, Int)
|
||||
.Replace(COLLATE_NOCASE, string.Empty)
|
||||
.Replace(BLOB, varbinary_max)
|
||||
.Replace(BLOB.ToLower(), varbinary_max)
|
||||
.Replace(NVCHAR, NVARCHAR)
|
||||
.Replace(NVCHAR.ToLower(), NVARCHAR).ToString();
|
||||
}
|
||||
|
||||
#region File List
|
||||
private static List<string> _fileList = new List<string>();
|
||||
|
||||
public static List<string> FileList { get => _fileList; set => _fileList = value; }
|
||||
|
||||
private static List<string> _newFileList = new List<string>();
|
||||
|
||||
public static List<string> NewFileList { get => _newFileList; set => _newFileList = value; }
|
||||
|
||||
/// <summary>
|
||||
/// recursively search directory and subdirectories and return a list of files
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="pattern">*.dts</param>
|
||||
/// <returns></returns>
|
||||
public static void FindFiles(string path, string pattern)
|
||||
{
|
||||
_fileList.AddRange(FindFiles(path, pattern, SearchOption.AllDirectories));
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Search directory and return a list of files
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="pattern">*.dts</param>
|
||||
/// <returns></returns>
|
||||
public static List<string> FindFilesInDirectory(string path, string pattern)
|
||||
{
|
||||
return FindFiles(path, pattern, SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
private static List<string> FindFiles(string path, string pattern, SearchOption searchOption)
|
||||
{
|
||||
return Directory.GetFiles(path, "*" + pattern, searchOption).
|
||||
Select(fn => new FileInfo(fn)).
|
||||
OrderBy(f => Regex.Replace(f.Name, @"\d+", n => n.Value.PadLeft(4, '0'))).
|
||||
Select(f => f.FullName).
|
||||
ToList();
|
||||
}
|
||||
|
||||
#endregion File List
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RM_UNIQUE_PROCESS
|
||||
{
|
||||
public int dwProcessId;
|
||||
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
|
||||
}
|
||||
|
||||
private const int RmRebootReasonNone = 0;
|
||||
private const int CCH_RM_MAX_APP_NAME = 255;
|
||||
private const int CCH_RM_MAX_SVC_NAME = 63;
|
||||
|
||||
private enum RM_APP_TYPE
|
||||
{
|
||||
RmUnknownApp = 0,
|
||||
RmMainWindow = 1,
|
||||
RmOtherWindow = 2,
|
||||
RmService = 3,
|
||||
RmExplorer = 4,
|
||||
RmConsole = 5,
|
||||
RmCritical = 1000
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct RM_PROCESS_INFO
|
||||
{
|
||||
public RM_UNIQUE_PROCESS Process;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
|
||||
public string strAppName;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)]
|
||||
public string strServiceShortName;
|
||||
|
||||
public RM_APP_TYPE ApplicationType;
|
||||
public uint AppStatus;
|
||||
public uint TSSessionId;
|
||||
[MarshalAs(UnmanagedType.Bool)]
|
||||
public bool bRestartable;
|
||||
}
|
||||
|
||||
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int RmRegisterResources(uint pSessionHandle,
|
||||
UInt32 nFiles,
|
||||
string[] rgsFilenames,
|
||||
UInt32 nApplications,
|
||||
[In] RM_UNIQUE_PROCESS[] rgApplications,
|
||||
UInt32 nServices,
|
||||
string[] rgsServiceNames);
|
||||
|
||||
[DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)]
|
||||
private static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey);
|
||||
|
||||
[DllImport("rstrtmgr.dll")]
|
||||
private static extern int RmEndSession(uint pSessionHandle);
|
||||
|
||||
[DllImport("rstrtmgr.dll")]
|
||||
private static extern int RmGetList(uint dwSessionHandle,
|
||||
out uint pnProcInfoNeeded,
|
||||
ref uint pnProcInfo,
|
||||
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
|
||||
ref uint lpdwRebootReasons);
|
||||
|
||||
/// <summary>
|
||||
/// Find out what process(es) have a lock on the specified file.
|
||||
/// </summary>
|
||||
/// <param name="path">Path of the file.</param>
|
||||
/// <returns>Processes locking the file</returns>
|
||||
/// <remarks>See also:
|
||||
/// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx
|
||||
/// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing)
|
||||
///
|
||||
/// </remarks>
|
||||
public static List<Process> WhoIsLocking(string path)
|
||||
{
|
||||
uint handle;
|
||||
string key = Guid.NewGuid().ToString();
|
||||
List<Process> processes = new List<Process>();
|
||||
|
||||
int res = RmStartSession(out handle, 0, key);
|
||||
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
|
||||
|
||||
try
|
||||
{
|
||||
const int ERROR_MORE_DATA = 234;
|
||||
uint pnProcInfoNeeded = 0,
|
||||
pnProcInfo = 0,
|
||||
lpdwRebootReasons = RmRebootReasonNone;
|
||||
|
||||
string[] resources = new string[] { path }; // Just checking on one resource.
|
||||
|
||||
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
|
||||
|
||||
if (res != 0) throw new Exception("Could not register resource.");
|
||||
|
||||
//Note: there's a race condition here -- the first call to RmGetList() returns
|
||||
// the total number of process. However, when we call RmGetList() again to get
|
||||
// the actual processes this number may have increased.
|
||||
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons);
|
||||
|
||||
if (res == ERROR_MORE_DATA)
|
||||
{
|
||||
// Create an array to store the process results
|
||||
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
|
||||
pnProcInfo = pnProcInfoNeeded;
|
||||
|
||||
// Get the list
|
||||
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
|
||||
if (res == 0)
|
||||
{
|
||||
processes = new List<Process>((int)pnProcInfo);
|
||||
|
||||
// Enumerate all of the results and add them to the
|
||||
// list to be returned
|
||||
for (int i = 0; i < pnProcInfo; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
|
||||
}
|
||||
// catch the error -- in case the process is no longer running
|
||||
catch (ArgumentException)
|
||||
{
|
||||
//ignore exception
|
||||
}
|
||||
}
|
||||
}
|
||||
else throw new Exception("Could not list processes locking resource.");
|
||||
}
|
||||
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
RmEndSession(handle);
|
||||
}
|
||||
|
||||
return processes;
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Common/DTS.Common/Utils/IPUtils.cs
Normal file
111
Common/DTS.Common/Utils/IPUtils.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class IPRange
|
||||
{
|
||||
/// <summary>
|
||||
/// ip range, or a start and a stop
|
||||
/// </summary>
|
||||
public IPAddressIntForm RangeStart { get; set; }
|
||||
public IPAddressIntForm RangeEnd { get; set; }
|
||||
public IPRange(IPAddressIntForm start, IPAddressIntForm end)
|
||||
{
|
||||
RangeStart = start;
|
||||
RangeEnd = end;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// just structure for ips broken into bytes
|
||||
/// </summary>
|
||||
public class IPAddressIntForm
|
||||
{
|
||||
public int ByteOne { get; }
|
||||
public int ByteTwo { get; }
|
||||
public int ByteThree { get; }
|
||||
public int ByteFour { get; }
|
||||
|
||||
public IPAddressIntForm(int b1, int b2, int b3, int b4)
|
||||
{
|
||||
ByteOne = b1;
|
||||
ByteTwo = b2;
|
||||
ByteThree = b3;
|
||||
ByteFour = b4;
|
||||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{ByteOne}.{ByteTwo}.{ByteThree}.{ByteFour}";
|
||||
}
|
||||
public static bool TryParse(string input, out IPAddressIntForm output)
|
||||
{
|
||||
var tokens = input.Split('.');
|
||||
if (4 != tokens.Length)
|
||||
{
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
if (int.TryParse(tokens[0], out int one)
|
||||
&& int.TryParse(tokens[1], out int two)
|
||||
&& int.TryParse(tokens[2], out int three)
|
||||
&& int.TryParse(tokens[3], out int four))
|
||||
{
|
||||
output = new IPAddressIntForm(one, two, three, four);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
output = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
internal class IPComparer : IComparer<IPAddressIntForm>
|
||||
{
|
||||
public int Compare(IPAddressIntForm x, IPAddressIntForm y)
|
||||
{
|
||||
if (x == y) { return 0; }
|
||||
if (null == x) { return -1; }
|
||||
if (null == y) { return 1; }
|
||||
var ret = x.ByteOne.CompareTo(y.ByteOne);
|
||||
if (ret != 0) { return ret; }
|
||||
ret = x.ByteTwo.CompareTo(y.ByteTwo);
|
||||
if (ret != 0) { return ret; }
|
||||
ret = x.ByteThree.CompareTo(y.ByteThree);
|
||||
if (ret != 0) { return ret; }
|
||||
ret = x.ByteFour.CompareTo(y.ByteFour);
|
||||
if (ret != 0) { return ret; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// gets all ips between a start and an end
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <returns></returns>
|
||||
public static string[] GetAllIPs(IPAddressIntForm a, IPAddressIntForm b)
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
var comparer = new IPComparer();
|
||||
IPAddressIntForm start = a;
|
||||
IPAddressIntForm end = b;
|
||||
if (comparer.Compare(a, b) > 0) { start = b; end = a; }
|
||||
|
||||
for( var firstByte = start.ByteOne; firstByte <= end.ByteOne; firstByte ++)
|
||||
{
|
||||
for( var secondByte = start.ByteTwo; secondByte <= end.ByteTwo; secondByte ++)
|
||||
{
|
||||
for ( var thirdByte = start.ByteThree; thirdByte <= end.ByteThree; thirdByte ++)
|
||||
{
|
||||
for ( var fourthByte = start.ByteFour; fourthByte <= end.ByteFour; fourthByte ++)
|
||||
{
|
||||
list.Add($"{firstByte}.{secondByte}.{thirdByte}.{fourthByte}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Common/DTS.Common/Utils/ImageButton.cs
Normal file
52
Common/DTS.Common/Utils/ImageButton.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class ImageButton : Button
|
||||
{
|
||||
private readonly Image _image;
|
||||
private readonly TextBlock _textBlock;
|
||||
|
||||
public ImageButton()
|
||||
{
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new System.Windows.Thickness(10)
|
||||
};
|
||||
|
||||
_image = new Image { Margin = new System.Windows.Thickness(0, 0, 0, 0), Stretch = Stretch.Fill };
|
||||
panel.Children.Add(_image);
|
||||
|
||||
_textBlock = new TextBlock();
|
||||
panel.Children.Add(_textBlock);
|
||||
|
||||
Content = panel;
|
||||
}
|
||||
|
||||
private ImageSource _source;
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
get => _source;
|
||||
set
|
||||
{
|
||||
_source = value;
|
||||
_image.Source = _source;
|
||||
}
|
||||
}
|
||||
|
||||
private string _imageText;
|
||||
|
||||
public string ImageText
|
||||
{
|
||||
get => _imageText;
|
||||
set
|
||||
{
|
||||
_imageText = value;
|
||||
_textBlock.Text = _imageText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Common/DTS.Common/Utils/MouseUtils.cs
Normal file
50
Common/DTS.Common/Utils/MouseUtils.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class MouseUtilities
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Win32Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
};
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetCursorPos(ref Win32Point pt);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetPhysicalCursorPos(ref Win32Point pt);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ScreenToClient(IntPtr hwnd, ref Win32Point pt);
|
||||
|
||||
public static Point GetMousePosition(Visual relativeTo)
|
||||
{
|
||||
var mouse = new Win32Point();
|
||||
GetCursorPos(ref mouse);
|
||||
|
||||
var presentationSource =
|
||||
(System.Windows.Interop.HwndSource)PresentationSource.FromVisual(relativeTo);
|
||||
|
||||
var factor = 2D - presentationSource.CompositionTarget.TransformToDevice.M22;
|
||||
|
||||
ScreenToClient(presentationSource.Handle, ref mouse);
|
||||
|
||||
var transform = relativeTo.TransformToAncestor(presentationSource.RootVisual);
|
||||
|
||||
var offset = transform.Transform(new Point(0, 0));
|
||||
if (factor > 0)
|
||||
{
|
||||
offset.Y = offset.Y + offset.Y * (presentationSource.CompositionTarget.TransformToDevice.M22 - 1);
|
||||
}
|
||||
return new Point(mouse.X - offset.X, mouse.Y - offset.Y);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
238
Common/DTS.Common/Utils/NetworkUtils.cs
Normal file
238
Common/DTS.Common/Utils/NetworkUtils.cs
Normal file
@@ -0,0 +1,238 @@
|
||||
using DTS.Common.Utilities.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
//FB 18512 This calss contains utility methods, they are moved from Netwroking module
|
||||
public static class NetworkUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// returns true if the network interface is up, false otherwise
|
||||
/// stolen from FWTU
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsNetworkInterfaceUp(IPAddress ip)
|
||||
{
|
||||
foreach (var a in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
try
|
||||
{
|
||||
var ipp = a.GetIPProperties();
|
||||
|
||||
if (ipp.MulticastAddresses.Count == 0)
|
||||
continue; // most of VPN adapters will be skipped
|
||||
if (!a.SupportsMulticast)
|
||||
continue; // multicast is meaningless for this type of connection
|
||||
if (OperationalStatus.Up != a.OperationalStatus)
|
||||
continue; // this adapter is off or not connected
|
||||
if (null == ipp.GetIPv4Properties())
|
||||
continue; // IPv4 is not configured on this adapter
|
||||
if (a.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
continue;
|
||||
var addressBytes = ip.GetAddressBytes();
|
||||
if (!AllowInternalNICIPs && 4 == addressBytes.Length && addressBytes[0] == 169 && addressBytes[1] == 254) { continue; }
|
||||
if (a.GetIPProperties().UnicastAddresses.Any(i => i.Address.AddressFamily == AddressFamily.InterNetwork && IPAddress.Equals(i.Address, ip)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Indicates whether any network connection is available.
|
||||
/// Filter connections below a specified speed, as well as virtual network cards.
|
||||
/// </summary>
|
||||
/// <param name="minimumSpeed">The minimum speed required. Passing 0 will not filter connection using speed.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if a network connection is available; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
private static List<NetworkInterface> GetAvailableNetworkInterfaces(long minimumSpeed = 115200)
|
||||
{
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
return null;
|
||||
|
||||
var niList = new List<NetworkInterface>();
|
||||
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
// discard because of standard reasons
|
||||
if ((ni.NetworkInterfaceType == NetworkInterfaceType.Loopback)
|
||||
|| (ni.NetworkInterfaceType == NetworkInterfaceType.Tunnel))
|
||||
continue;
|
||||
|
||||
// this allow to filter modems, serial, etc.
|
||||
// use 10000000 as a minimum speed for most cases
|
||||
if (ni.Speed < minimumSpeed)
|
||||
continue;
|
||||
|
||||
// discard virtual cards (virtual box, virtual pc, etc.)
|
||||
if (ni.Description.ToLower().Contains("virtual")
|
||||
|| ni.Name.ToLower().Contains("virtual")
|
||||
|| ni.Description.ToLower().Contains("bluethooth")
|
||||
|| ni.Name.ToLower().Contains("bluethooth"))
|
||||
continue;
|
||||
|
||||
// discard "Microsoft Loopback Adapter", it will not show as NetworkInterfaceType.Loopback but as Ethernet Card.
|
||||
if (ni.Description.Equals("Microsoft Loopback Adapter", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
niList.Add(ni);
|
||||
}
|
||||
|
||||
return niList;
|
||||
}
|
||||
|
||||
private static string GetMACAddress(string id)
|
||||
{
|
||||
var macAddress = string.Empty;
|
||||
|
||||
var nics = GetAvailableNetworkInterfaces();
|
||||
|
||||
if (nics == null)
|
||||
return macAddress;
|
||||
|
||||
foreach (var nic in nics)
|
||||
{
|
||||
if (nic.Id != id) continue;
|
||||
var pa = BitConverter.ToString(nic.GetPhysicalAddress().GetAddressBytes());
|
||||
pa = pa.Replace("-", ":");
|
||||
if (string.IsNullOrEmpty(pa)) continue;
|
||||
macAddress = pa;
|
||||
break;
|
||||
}
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
private static bool InvalidNIC(NetworkInterface ni)
|
||||
{
|
||||
return OperationalStatus.Up != ni.OperationalStatus || !ni.SupportsMulticast;
|
||||
}
|
||||
public static bool AllowInternalNICIPs { get; set; } = false;
|
||||
public static List<HostInfo> GetAvailableHosts(bool supportMulticastOnly = false)
|
||||
{
|
||||
var nics = GetAvailableNetworkInterfaces();
|
||||
if (nics == null) { return new List<HostInfo>(); }
|
||||
var hosts = new List<HostInfo>();
|
||||
|
||||
foreach (NetworkInterface ni in nics)
|
||||
{
|
||||
if (supportMulticastOnly && InvalidNIC(ni))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//exclude wireshark network interface, it claims it can ping ips lyingly
|
||||
if (ni.Description.Contains("Npcap")) { continue; }
|
||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
var addressBytes = ip.Address.GetAddressBytes();
|
||||
if ( !AllowInternalNICIPs && 4 == addressBytes.Length && addressBytes[0] == 169 && addressBytes[1] == 254)
|
||||
{
|
||||
APILogger.Log($"skipping {ni.Name}\\{ni.Description} as it has a 169.254 address");
|
||||
continue;//internal only
|
||||
}
|
||||
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
GetStartAndEndAddress(ip.Address, ip.IPv4Mask, out var startAddress, out var endAddress);
|
||||
hosts.Add(new HostInfo
|
||||
{
|
||||
HostIpAddress = ip.Address.ToString(),
|
||||
HostNetworkId = ni.Id,
|
||||
HostMacAddress = GetMACAddress(ni.Id),
|
||||
StartAddress = startAddress,
|
||||
EndAddress = endAddress
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return hosts;
|
||||
}
|
||||
/// <summary>
|
||||
/// given an IP address and a mask returns a ip address as a string for the first address that is accessible and a string for the last address that is accessible
|
||||
/// anything between the start address and end address after doing a byte-by-byte comparison should be addressable
|
||||
/// will return empty strings if something bad happens. Is designed for 4 byte addresses but isn't necessarily limited.
|
||||
/// </summary>
|
||||
/// <param name="ip"></param>
|
||||
/// <param name="mask"></param>
|
||||
/// <param name="startAddress"></param>
|
||||
/// <param name="endAddress"></param>
|
||||
private static void GetStartAndEndAddress(IPAddress ip, IPAddress mask, out string startAddress, out string endAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
var ipByte = ip.GetAddressBytes();
|
||||
var maskBytes = mask.GetAddressBytes();
|
||||
|
||||
var startBytes = new byte[ipByte.Length];
|
||||
var endBytes = new byte[ipByte.Length];
|
||||
|
||||
for (var i = 0; i < ipByte.Length; i++)
|
||||
{
|
||||
startBytes[i] = (byte)(ipByte[i] & maskBytes[i]);
|
||||
endBytes[i] = (byte)(ipByte[i] | ~maskBytes[i]);
|
||||
}
|
||||
var startIP = new IPAddress(startBytes);
|
||||
var endIP = new IPAddress(endBytes);
|
||||
startAddress = startIP.ToString();
|
||||
endAddress = endIP.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
startAddress = string.Empty;
|
||||
endAddress = string.Empty;
|
||||
APILogger.Log(ex);
|
||||
}
|
||||
}
|
||||
//FB 25658 Changed the method signature to return bool to determine the connection string has IP in it and it's not USB
|
||||
/// <summary>
|
||||
/// Try to parse connectionString and retrieve the IP address.
|
||||
/// </summary>
|
||||
/// <param name="connectionString"> DAS connectionString which might include the IP address or might be a USB connection string</param>
|
||||
/// <param name="ipAddress">Return the parsed IP address from connectionString</param>
|
||||
/// <returns>Returns true if was able to parse and get the IP otherwise false including the valid USB connection string </returns>
|
||||
public static bool TryParseConnectionString(string connectionString, out string ipAddress)
|
||||
{
|
||||
ipAddress = null;
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(connectionString);
|
||||
|
||||
if (IPAddress.TryParse(uri.Host, out _))
|
||||
{
|
||||
ipAddress = uri.Host;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
var ip = connectionString.Split(':')[0];
|
||||
|
||||
if (IPAddress.TryParse(ip, out _))
|
||||
{
|
||||
ipAddress = ip;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Common/DTS.Common/Utils/PNGImageUtil.cs
Normal file
38
Common/DTS.Common/Utils/PNGImageUtil.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// this class holds PNG image functions
|
||||
/// </summary>
|
||||
public static class PNGImageUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// renders the given element as a png to the given filepath
|
||||
/// </summary>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="fileName"></param>
|
||||
public static void SaveImage(FrameworkElement view, string fileName)
|
||||
{
|
||||
var size = new Size(view.ActualWidth, view.ActualHeight);
|
||||
var rtb = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Pbgra32);
|
||||
var visual = new DrawingVisual();
|
||||
using (var context = visual.RenderOpen())
|
||||
{
|
||||
context.DrawRectangle(new VisualBrush(view), null, new Rect(new Point(), size));
|
||||
context.Close();
|
||||
}
|
||||
|
||||
rtb.Render(visual);
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(rtb));
|
||||
|
||||
using (var stream = new System.IO.FileStream(fileName, System.IO.FileMode.CreateNew))
|
||||
{
|
||||
encoder.Save(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
870
Common/DTS.Common/Utils/PingUtils.cs
Normal file
870
Common/DTS.Common/Utils/PingUtils.cs
Normal file
@@ -0,0 +1,870 @@
|
||||
using DTS.Common.Interface.StatusAndProgressBar;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class PingUtils
|
||||
{
|
||||
public static void EliminateBadHosts(ref List<HostInfo> list, string[] ips)
|
||||
{
|
||||
var hosts = list.ToArray();
|
||||
foreach (var host in hosts)
|
||||
{
|
||||
if (!HostMatchesAnyIPs(host, ips))
|
||||
{
|
||||
APILogger.Log($"Removing host {host.HostIpAddress} as it can't reach any ips");
|
||||
list.Remove(host);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// returns false if the host can not access any ips provided after looking at it's ip address and netmask
|
||||
/// returns true if it can reach an ip or it can't be determined if it can reach an IP this might happen if someone used
|
||||
/// a host name instead of a xxx.yyy.zzz.aaa style address or an empty string somewhere
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="ips"></param>
|
||||
/// <returns></returns>
|
||||
protected static bool HostMatchesAnyIPs(HostInfo host, string[] ips)
|
||||
{
|
||||
if (ips.Length == 0) { return true; }
|
||||
if (string.Empty.Equals(host.StartAddress) || string.Empty.Equals(host.EndAddress)) { return true; }
|
||||
//FB 43919
|
||||
List<bool> result = new List<bool>();
|
||||
foreach (var ip in ips)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IPAddress.TryParse(ip, out var ipToConnectTo))
|
||||
{
|
||||
//it's not a traditional format ip, give up trying to match it
|
||||
return true;
|
||||
}
|
||||
if (!IPAddress.TryParse(host.StartAddress, out var startAddress))
|
||||
{
|
||||
//start address is not a traditional format, give up trying to match
|
||||
return true;
|
||||
}
|
||||
if (!IPAddress.TryParse(host.EndAddress, out var endAddress))
|
||||
{
|
||||
//start address is not in traditional format, give up trying to match
|
||||
return true;
|
||||
}
|
||||
var startBytes = startAddress.GetAddressBytes();
|
||||
var endBytes = endAddress.GetAddressBytes();
|
||||
var ipByte = ipToConnectTo.GetAddressBytes();
|
||||
|
||||
for (var i = 0; i < startBytes.Length && i < endBytes.Length && i < ipByte.Length; i++)
|
||||
{
|
||||
//FB 43919 If this condition is true as first iteration then break immediately and result collection should have only one false
|
||||
//which means we need to remove this ip since it's not in the network
|
||||
if (ipByte[i] < startBytes[i] || ipByte[i] > endBytes[i])
|
||||
{
|
||||
result.Add(false);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Add(true);
|
||||
}
|
||||
}
|
||||
//FB 43919 if any of the segment was true then don't remove the ip
|
||||
return result.Exists(p => p);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.Log(ex);
|
||||
}
|
||||
}
|
||||
//FB 43919
|
||||
return false;
|
||||
}
|
||||
//FB 18152 & 25642 This dictionary keeps the host info for each DAS
|
||||
public static ConcurrentDictionary<string, HostInfo> DasToHost { get; set; } = new ConcurrentDictionary<string, HostInfo>();
|
||||
public class PingDevice
|
||||
{
|
||||
|
||||
private const int MAX_PING_ATTEMPTS = 3;
|
||||
private const string ALPHABET = "abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
#region Ping
|
||||
|
||||
public bool PingDevices(List<string> ipList)
|
||||
{
|
||||
//FB 18152 Get the avilable hosts
|
||||
var hostInfos = NetworkUtils.GetAvailableHosts();
|
||||
EliminateBadHosts(ref hostInfos, ipList?.ToArray());
|
||||
return PingAllDevices(hostInfos, ipList);
|
||||
}
|
||||
|
||||
#region Ping All Devices
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// ICMP ping
|
||||
/// </summary>
|
||||
/// <param name="hostIpAddress">host IP address</param>
|
||||
/// <param name="ipList">List of connected devices IP addresses </param>
|
||||
/// <returns>false - if as least one device disconnected</returns>
|
||||
private bool PingAllDevices(List<HostInfo> hostInfos, List<string> ipList)
|
||||
{
|
||||
var hostStringList = new List<string>();
|
||||
foreach (var host in hostInfos)
|
||||
{
|
||||
hostStringList.Add(host.HostIpAddress);
|
||||
}
|
||||
APILogger.Log($"trying to ping {string.Join(", ", ipList.ToArray())} using hosts: {string.Join(", ", hostStringList.ToArray())}");
|
||||
var allConnected = true;
|
||||
Parallel.ForEach(ipList, (item, state) =>
|
||||
{
|
||||
if (hostInfos.Any())
|
||||
{
|
||||
var lastHost = hostInfos.Last();
|
||||
//FB 18152 & 25642 Perform the ping on all the devices in all hosts
|
||||
foreach (var hostInfo in hostInfos)
|
||||
{
|
||||
var eachConnected = PingOneDevice(item, hostInfo.HostIpAddress);
|
||||
if (eachConnected)
|
||||
{
|
||||
//We found the host update the dictionary and break from the loop
|
||||
DasToHost[item] = hostInfo;
|
||||
allConnected = eachConnected;
|
||||
APILogger.Log($"pinging {string.Join(", ", ipList.ToArray())} worked for {hostInfo.HostIpAddress}");
|
||||
break;
|
||||
}
|
||||
if (hostInfo.Equals(lastHost))
|
||||
{
|
||||
//all the hosts are processed
|
||||
allConnected = eachConnected;
|
||||
}
|
||||
}
|
||||
if (!allConnected) state.Break();
|
||||
}
|
||||
});
|
||||
if (allConnected) { APILogger.Log("An interface could ping"); }
|
||||
else { APILogger.Log("no intefaces could ping"); }
|
||||
return allConnected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP ping
|
||||
/// </summary>
|
||||
/// <param name="ipList">List of connected devices IP addresses </param>
|
||||
/// <returns>false - if as least one device disconnected</returns>
|
||||
private bool PingAllDevices(List<string> ipList)
|
||||
{
|
||||
var allConnected = true;
|
||||
Parallel.ForEach(ipList, (item, state) =>
|
||||
{
|
||||
allConnected = PingOneDevice(item);
|
||||
if (!allConnected) state.Break();
|
||||
});
|
||||
return allConnected;
|
||||
}
|
||||
|
||||
#endregion Ping All Devices
|
||||
|
||||
#region Ping One Device
|
||||
/// <summary>
|
||||
/// TCP ping
|
||||
/// </summary>
|
||||
/// <param name="deviceIpAddress">device IP addresses </param>
|
||||
/// <returns>false - if device disconnected</returns>
|
||||
private bool PingOneDevice(string deviceIpAddress)
|
||||
{
|
||||
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
|
||||
{
|
||||
var pingSender = new Ping();
|
||||
|
||||
var reply = pingSender.Send(deviceIpAddress);
|
||||
if (reply != null && reply.Status == IPStatus.Success) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ICMP ping
|
||||
/// </summary>
|
||||
/// <param name="deviceIp">device IP addresses </param>
|
||||
/// <param name="hostIpAddress">host IP address</param>
|
||||
/// <returns>false - if device disconnected</returns>
|
||||
private bool PingOneDevice(string deviceIp, string hostIpAddress)
|
||||
{
|
||||
for (var i = 0; i < MAX_PING_ATTEMPTS; i++)
|
||||
{
|
||||
if (PingICMP(IPAddress.Parse(hostIpAddress), IPAddress.Parse(deviceIp), Constants.PING_ICMP_TIMEOUT, Encoding.ASCII.GetBytes(ALPHABET)).Status == IPStatus.Success)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion Ping One Device
|
||||
|
||||
#region ICMP Ping
|
||||
|
||||
/// <summary>
|
||||
/// ICMP ping
|
||||
/// </summary>
|
||||
/// <param name="srcAddress">host address</param>
|
||||
/// <param name="destAddress">device address</param>
|
||||
/// <param name="timeout">timeout</param>
|
||||
/// <param name="buffer">Function will fail without buffer (used alphabet)</param>
|
||||
/// <param name="po">Ping options - not been usexd</param>
|
||||
/// <returns>PingReplyUtils class</returns>
|
||||
private static PingReplyUtils PingICMP(IPAddress srcAddress, IPAddress destAddress, int timeout = 5000, byte[] buffer = null, PingOptions po = null)
|
||||
{
|
||||
var ipSrc = string.Empty;
|
||||
var ipDest = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var ipSrcBytes = srcAddress?.GetAddressBytes() ?? new byte[0];
|
||||
if (4 <= ipSrcBytes.Length) { ipSrc = $"{ipSrcBytes[0]}.{ipSrcBytes[1]}.{ipSrcBytes[2]}.{ipSrcBytes[3]}"; }
|
||||
var destBytes = destAddress?.GetAddressBytes() ?? new byte[0];
|
||||
if (4 <= destBytes.Length) { ipDest = $"{destBytes[0]}.{destBytes[1]}.{destBytes[2]}.{destBytes[3]}"; }
|
||||
}
|
||||
catch (Exception ex) { APILogger.Log(ex); }
|
||||
if (destAddress == null || destAddress.AddressFamily != AddressFamily.InterNetwork ||
|
||||
destAddress.Equals(IPAddress.Any))
|
||||
{
|
||||
APILogger.Log($"Ping -S {ipSrc} {ipDest} failed - Argument exception");
|
||||
throw new ArgumentException(string.Format("PingAll failed on IP:{0}", srcAddress));
|
||||
}
|
||||
|
||||
//Defining pinvoke args
|
||||
var source = srcAddress == null ? 0 : BitConverter.ToUInt32(srcAddress.GetAddressBytes(), 0);
|
||||
var destination = BitConverter.ToUInt32(destAddress.GetAddressBytes(), 0);
|
||||
var sendbuffer = buffer ?? new byte[] { };
|
||||
var options = new Interop.Option
|
||||
{
|
||||
Ttl = (po == null ? (byte)255 : (byte)po.Ttl),
|
||||
Flags = (po == null ? (byte)0 : po.DontFragment ? (byte)0x02 : (byte)0) //0x02
|
||||
};
|
||||
var fullReplyBufferSize = Interop.ReplyMarshalLength + sendbuffer.Length;
|
||||
//Size of Reply struct and the transmitted buffer length.
|
||||
|
||||
|
||||
|
||||
var allocSpace = Marshal.AllocHGlobal(fullReplyBufferSize);
|
||||
// unmanaged allocation of reply size. TODO Maybe should be allocated on stack
|
||||
try
|
||||
{
|
||||
var start = DateTime.Now;
|
||||
var nativeCode = Interop.IcmpSendEcho2Ex(
|
||||
/* _In_ HANDLE IcmpHandle, */
|
||||
Interop.IcmpHandle,
|
||||
/* _In_opt_ HANDLE Event, */
|
||||
default(IntPtr),
|
||||
/* _In_opt_ PIO_APC_ROUTINE ApcRoutine, */
|
||||
default(IntPtr),
|
||||
/* _In_opt_ PVOID ApcContext */
|
||||
default(IntPtr),
|
||||
/* _In_ IPAddr SourceAddress, */
|
||||
source,
|
||||
/* _In_ IPAddr DestinationAddress, */
|
||||
destination,
|
||||
/* _In_ LPVOID RequestData, */
|
||||
sendbuffer,
|
||||
/* _In_ WORD RequestSize, */
|
||||
(short)sendbuffer.Length,
|
||||
/* _In_opt_ PIP_OPTION_INFORMATION RequestOptions, */
|
||||
ref options,
|
||||
/* _Out_ LPVOID ReplyBuffer, */
|
||||
allocSpace,
|
||||
/* _In_ DWORD ReplySize, */
|
||||
fullReplyBufferSize,
|
||||
/* _In_ DWORD Timeout */
|
||||
timeout);
|
||||
var duration = DateTime.Now - start;
|
||||
var reply = (Interop.Reply)Marshal.PtrToStructure(allocSpace, typeof(Interop.Reply));
|
||||
// Parse the beginning of reply memory to reply struct
|
||||
|
||||
byte[] replyBuffer = null;
|
||||
if (sendbuffer.Length != 0)
|
||||
{
|
||||
replyBuffer = new byte[sendbuffer.Length];
|
||||
Marshal.Copy(allocSpace + Interop.ReplyMarshalLength, replyBuffer, 0, sendbuffer.Length);
|
||||
//copy the rest of the reply memory to managed byte[]
|
||||
}
|
||||
|
||||
var errorCode = 0;
|
||||
if (0 != nativeCode)
|
||||
{
|
||||
errorCode = Marshal.GetLastWin32Error();
|
||||
}
|
||||
APILogger.Log($"Ping -S {ipSrc} {ipDest} result: {reply.Status} LastError: {errorCode}");
|
||||
return nativeCode == 0
|
||||
? new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), duration)
|
||||
: new PingReplyUtils(nativeCode, reply.Status, new IPAddress(reply.Address), reply.RoundTripTime,
|
||||
replyBuffer);
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(allocSpace); /*free allocated space*/ }
|
||||
}
|
||||
|
||||
#endregion ICMP Ping
|
||||
|
||||
#region Support ICMP Ping
|
||||
|
||||
/// <summary>Interoperability Helper
|
||||
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/bb309069(v=vs.85).aspx" />
|
||||
/// </summary>
|
||||
private static class Interop
|
||||
{
|
||||
private static IntPtr? _icmpHandle;
|
||||
private static int? _replyStructLength;
|
||||
|
||||
/// <summary>Returns the application legal icmp handle. Should be close by IcmpCloseHandle
|
||||
/// <see cref="http://msdn.microsoft.com/en-us/library/windows/desktop/aa366045(v=vs.85).aspx" />
|
||||
/// </summary>
|
||||
public static IntPtr IcmpHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_icmpHandle == null)
|
||||
{
|
||||
_icmpHandle = IcmpCreateFile();
|
||||
//TODO Close Icmp Handle appropiate
|
||||
}
|
||||
|
||||
return _icmpHandle.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Returns the the marshaled size of the reply struct.</summary>
|
||||
public static int ReplyMarshalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_replyStructLength == null)
|
||||
{
|
||||
_replyStructLength = Marshal.SizeOf(typeof(Reply));
|
||||
}
|
||||
return _replyStructLength.GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
||||
private static extern IntPtr IcmpCreateFile();
|
||||
|
||||
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
||||
private static extern bool IcmpCloseHandle(IntPtr handle);
|
||||
|
||||
[DllImport("Iphlpapi.dll", SetLastError = true)]
|
||||
public static extern uint IcmpSendEcho2Ex(IntPtr icmpHandle, IntPtr Event, IntPtr apcroutine,
|
||||
IntPtr apccontext, uint sourceAddress, uint destinationAddress, byte[] requestData,
|
||||
short requestSize, ref Option requestOptions, IntPtr replyBuffer, int replySize, int timeout);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct Option
|
||||
{
|
||||
public byte Ttl;
|
||||
public readonly byte Tos;
|
||||
public byte Flags;
|
||||
public readonly byte OptionsSize;
|
||||
public readonly IntPtr OptionsData;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct Reply
|
||||
{
|
||||
public readonly uint Address;
|
||||
public readonly int Status;
|
||||
public readonly int RoundTripTime;
|
||||
public readonly short DataSize;
|
||||
public readonly short Reserved;
|
||||
public readonly IntPtr DataPtr;
|
||||
public readonly Option Options;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Support ICMP Ping
|
||||
|
||||
#endregion Ping
|
||||
private static readonly object LogLock = new object();
|
||||
public static void PingLog(params string[] args)
|
||||
{
|
||||
var sb = new StringBuilder(200);
|
||||
sb.AppendFormat("=== {0} ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
foreach (var arg in args)
|
||||
{
|
||||
sb.Append(" ");
|
||||
sb.Append(arg);
|
||||
}
|
||||
sb.Append(Environment.NewLine);
|
||||
lock (LogLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.AppendAllText("Logs/ping.log", sb.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
#region OldPing
|
||||
public void PingFunction(object o)
|
||||
{
|
||||
var td = o as PingThreadData;
|
||||
if (td == null) return;
|
||||
td.UpdateProgress(0D);
|
||||
if (null == td.DoneEvent)
|
||||
{
|
||||
td.DoneEvent = new ManualResetEvent(false);
|
||||
}
|
||||
if (null == td.CancelEvent)
|
||||
{
|
||||
td.CancelEvent = new ManualResetEvent(false);
|
||||
}
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendFormat("PingFunction ");
|
||||
foreach (var item in td.Entries)
|
||||
{
|
||||
sb.AppendFormat("{0}, ", item.IPAddress);
|
||||
}
|
||||
PingLog(sb.ToString());
|
||||
|
||||
if (0 == td.Entries.Length)
|
||||
{
|
||||
td.UpdateProgress(100D);
|
||||
Thread.Sleep(100);
|
||||
td.Complete();
|
||||
return;
|
||||
}
|
||||
// Kick off the background thread while we monitor/simulate progress.
|
||||
Task.Run(() => CheckOnline(td)).ConfigureAwait(false);
|
||||
// FB 25642 Consider the number of hosts as well
|
||||
var maxTime = Constants.PING_ICMP_TIMEOUT * td.Entries.Length * NetworkUtils.GetAvailableHosts().Count;
|
||||
if (maxTime > 0 && maxTime < 1000)
|
||||
{
|
||||
maxTime = 1000; //minimum of 1 second
|
||||
}
|
||||
// Tick along progress as if it will take MAXPINGTIME. If we finish early, great.
|
||||
var timeWaited = 0D;
|
||||
while (!td.DoneEvent.WaitOne(150, false) && !td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
timeWaited += 150D;
|
||||
if (timeWaited >= maxTime)
|
||||
{
|
||||
PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=", maxTime.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
td.Complete();
|
||||
}
|
||||
//else if (timeWaited >= Maxpingtime)
|
||||
//{
|
||||
// PingLog("WARNING timewaited >= maxpingtime: ", timeWaited.ToString(), ">=",
|
||||
// Maxpingtime.ToString());
|
||||
// //td.CancelEvent.Set();
|
||||
// //td.Complete();
|
||||
// //td.DoneEvent.WaitOne();
|
||||
// Thread.Sleep(100);
|
||||
//}
|
||||
else
|
||||
{
|
||||
td.UpdateProgress(100D);
|
||||
Thread.Sleep(100);
|
||||
td.Complete();
|
||||
}
|
||||
}
|
||||
//used to hold the number of ips pinged completed while actively pinging
|
||||
private volatile int _pingsDone = 0;
|
||||
/// <summary>
|
||||
/// Pinging device from specific IP address function will use IMC protocol, TC Protocol will be used if host IP address is empty
|
||||
/// </summary>
|
||||
/// <param name="o">A <see cref="PingThreadData"/> object</param>
|
||||
private void CheckOnline(object o)
|
||||
{
|
||||
var td = o as PingThreadData;
|
||||
if (td != null && null == td.CancelEvent)
|
||||
{
|
||||
td.CancelEvent = new ManualResetEvent(false);
|
||||
}
|
||||
try
|
||||
{
|
||||
var maxPingTime = Convert.ToInt32(Constants.PING_ICMP_TIMEOUT);
|
||||
if (td == null) return;
|
||||
var ipList = new List<string>();
|
||||
foreach (var entry in td.Entries)
|
||||
{
|
||||
ipList.Add(entry.IPAddress);
|
||||
}
|
||||
var hostInfos = NetworkUtils.GetAvailableHosts();
|
||||
EliminateBadHosts(ref hostInfos, ipList.ToArray());
|
||||
if (!hostInfos.Any()) { return; }
|
||||
var lastHost = hostInfos.Last();
|
||||
var alphabet = Encoding.ASCII.GetBytes(ALPHABET);
|
||||
var toPing = td.Entries.Length;
|
||||
_pingsDone = 0;
|
||||
_ = Parallel.ForEach(td.Entries, item =>
|
||||
{
|
||||
if (td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
||||
return;
|
||||
}
|
||||
td.UpdateEntry(item, PingProgressStates.Pinging, 0);
|
||||
var succeded = false;
|
||||
var attempt = 0;
|
||||
|
||||
//FB 25642 Get available hosts
|
||||
|
||||
//avoid having to re-parse the ip address x thousand of times
|
||||
var lookup = new Dictionary<HostInfo, IPAddress>();
|
||||
foreach (var hostInfo in hostInfos)
|
||||
{
|
||||
lookup[hostInfo] = IPAddress.Parse(hostInfo.HostIpAddress);
|
||||
}
|
||||
while (!succeded && attempt < MAX_PING_ATTEMPTS && !td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
if (td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
PingReplyUtils allReply = null;
|
||||
//FB 25642 we need to go through all the hosts
|
||||
foreach (var hostInfo in hostInfos)
|
||||
{
|
||||
if (!IPAddress.TryParse(item.IPAddress, out var ip))
|
||||
{
|
||||
attempt = MAX_PING_ATTEMPTS;
|
||||
continue;
|
||||
}
|
||||
//won't wpork if buffer is empty
|
||||
|
||||
var eachReply = PingICMP(lookup[hostInfo], ip, Constants.PING_ICMP_TIMEOUT, alphabet);
|
||||
if ((eachReply.Status == IPStatus.Success) && (eachReply.NativeCode != 0))
|
||||
{
|
||||
//we found the host, update the dictionary & break
|
||||
DasToHost[item.IPAddress] = hostInfo;
|
||||
allReply = eachReply;
|
||||
break;
|
||||
}
|
||||
if (hostInfo.Equals(lastHost))
|
||||
{
|
||||
//if it's the last host update the overall reply
|
||||
allReply = eachReply;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//If the default gateway of the network interface isn't set appropriately, the call to PingICMP will
|
||||
//return with a Status of IPStatus.Success. So, to differentiate this from a valid successful ping, we check NativeCode
|
||||
|
||||
if (allReply != null)
|
||||
{
|
||||
//FB 25642 allReply shows the ping was successfull or not
|
||||
if ((allReply.Status == IPStatus.Success) && (allReply.NativeCode != 0))
|
||||
{
|
||||
succeded = true;
|
||||
td.UpdateEntry(item, PingProgressStates.Ping_Good, allReply.RoundTripTime.Milliseconds);
|
||||
PingLog("ping succeeded: ", DasToHost[item.IPAddress]?.HostIpAddress, " -> ", item.IPAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
attempt++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PingLog("couldn't parse ip: ", item.IPAddress);
|
||||
attempt++;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
td.UpdateEntry(item, PingProgressStates.InvalidIPAddress, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (succeded)
|
||||
{
|
||||
_pingsDone++;
|
||||
td.UpdateProgress(100D * _pingsDone / toPing);
|
||||
}
|
||||
else if (MAX_PING_ATTEMPTS <= attempt)
|
||||
{
|
||||
_pingsDone++;
|
||||
td.UpdateEntry(item, PingProgressStates.NoReply, 0);
|
||||
td.UpdateProgress(100D * _pingsDone / toPing);
|
||||
PingLog("failed (maxpings <= attempt: ", item.IPAddress, " - ", MAX_PING_ATTEMPTS.ToString(), "<=", attempt.ToString());
|
||||
}
|
||||
|
||||
if (td.CancelEvent.WaitOne(0, false))
|
||||
{
|
||||
td.UpdateEntry(item, PingProgressStates.Cancel, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.Log(ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_ = td.DoneEvent.Set();
|
||||
}
|
||||
}
|
||||
#endregion OldPing
|
||||
}
|
||||
}
|
||||
|
||||
#region PingReply
|
||||
[Serializable]
|
||||
public class PingReplyUtils
|
||||
{
|
||||
private Win32Exception _exception;
|
||||
|
||||
public PingReplyUtils()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, TimeSpan duration)
|
||||
{
|
||||
NativeCode = nativeCode;
|
||||
IpAddress = ipAddress;
|
||||
if (Enum.IsDefined(typeof(IPStatus), replystatus))
|
||||
{
|
||||
Status = (IPStatus)replystatus;
|
||||
}
|
||||
}
|
||||
|
||||
internal PingReplyUtils(uint nativeCode, int replystatus, IPAddress ipAddress, int roundTripTime,
|
||||
byte[] buffer)
|
||||
{
|
||||
NativeCode = nativeCode;
|
||||
IpAddress = ipAddress;
|
||||
RoundTripTime = TimeSpan.FromMilliseconds(roundTripTime);
|
||||
Buffer = buffer;
|
||||
if (Enum.IsDefined(typeof(IPStatus), replystatus))
|
||||
{
|
||||
Status = (IPStatus)replystatus;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>Native result from <code>IcmpSendEcho2Ex</code>.</summary>
|
||||
public uint NativeCode { get; } = 0;
|
||||
|
||||
public IPStatus Status { get; } = IPStatus.Unknown;
|
||||
|
||||
/// <summary>The source address of the reply.</summary>
|
||||
public IPAddress IpAddress { get; } = null;
|
||||
|
||||
public byte[] Buffer { get; } = null;
|
||||
|
||||
public TimeSpan RoundTripTime { get; } = TimeSpan.Zero;
|
||||
|
||||
/// <summary>Resolves the <code>Win32Exception</code> from native code</summary>
|
||||
public Win32Exception Exception
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Status != IPStatus.Success)
|
||||
{
|
||||
return _exception ?? (_exception = new Win32Exception((int)NativeCode, Status.ToString()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (Status == IPStatus.Success)
|
||||
{
|
||||
return Status + " from " + IpAddress + " in " + RoundTripTime + " ms with " + Buffer.Length +
|
||||
" bytes";
|
||||
}
|
||||
if (Status != IPStatus.Unknown)
|
||||
{
|
||||
return Status + " from " + IpAddress;
|
||||
}
|
||||
|
||||
return Exception.Message + " from " + IpAddress;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion PingReply
|
||||
|
||||
#region Entry
|
||||
/// <summary>
|
||||
/// Trivial class to represent an IP address entry, its type, and a visual representation
|
||||
/// </summary>
|
||||
public class Entry
|
||||
{
|
||||
public string IPAddress { get; set; }
|
||||
public int DasType { get; set; }
|
||||
public DataRow DR { get; set; }
|
||||
public object UserData { get; set; }
|
||||
}
|
||||
#endregion Entry
|
||||
|
||||
#region HostInfo
|
||||
//FB 25642 & 25590 & 18152 This class encapsulate information for the host in one place
|
||||
public class HostInfo
|
||||
{
|
||||
public string HostIpAddress { get; set; } = string.Empty;
|
||||
public string HostMacAddress { get; set; } = string.Empty;
|
||||
public string HostNetworkId { get; set; } = string.Empty;
|
||||
|
||||
public string StartAddress { get; set; } = string.Empty;
|
||||
public string EndAddress { get; set; } = string.Empty;
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PingProgressStates
|
||||
/// <summary>
|
||||
/// The possible states for a given <see cref="Entry"/>.
|
||||
/// </summary>
|
||||
public enum PingProgressStates
|
||||
{
|
||||
Pinging,
|
||||
Connecting,
|
||||
Querying,
|
||||
Added,
|
||||
Updated,
|
||||
InvalidIPAddress,
|
||||
NoReply,
|
||||
Ping_Good,
|
||||
Online,
|
||||
Connected,
|
||||
NoConnection,
|
||||
Cancel,
|
||||
Ready,
|
||||
Armed,
|
||||
QueryFailed,
|
||||
QueryTimedOut,
|
||||
QueryComplete,
|
||||
UnexpectedMaxMemory,
|
||||
UnexpectedFirmwareVersion,
|
||||
Passed,
|
||||
ExpiredCalDate,
|
||||
ChannelCountMismatch,
|
||||
MissingModules,
|
||||
Canceled,
|
||||
NoMemory,
|
||||
AutoArmed,
|
||||
Realtime,
|
||||
ReadyToStream,
|
||||
Streaming,
|
||||
LostDock,
|
||||
Rebooting,
|
||||
UnexpectedFirstUseDate,
|
||||
InvalidRecordingMode,
|
||||
InvalidStreamingMode,
|
||||
StreamingNotAvailable
|
||||
}
|
||||
#endregion PingProgressStates
|
||||
|
||||
#region PingThreadData
|
||||
/// <summary>
|
||||
/// A wrapper class to represent the progress and feedback for ping activity from background threads.
|
||||
/// </summary>
|
||||
public class PingThreadData
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Entry"/> list to act on.
|
||||
/// </summary>
|
||||
public Entry[] Entries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional <see cref="System.Threading.ManualResetEvent"/> to tell the process to cancel.
|
||||
/// </summary>
|
||||
public ManualResetEvent CancelEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional <see cref="System.Threading.ManualResetEvent"/> to fire when the entire process is done.
|
||||
/// </summary>
|
||||
public ManualResetEvent DoneEvent { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An optional delgate to be called whenever status text has changed.
|
||||
/// </summary>
|
||||
public SetStatusTextDelegate setStatusTextCallback;
|
||||
|
||||
/// <summary>
|
||||
/// An optional delgate to be called whenever progress has changed.
|
||||
/// </summary>
|
||||
public SetProgressValueDelegate progressCallback;
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used for reporting progress on an individual entry
|
||||
/// </summary>
|
||||
/// <param name="entry"><see cref="Entry"/> that is being reported on</param>
|
||||
/// <param name="state"><see cref="PingProgressStates"/> indicating the new state</param>
|
||||
public delegate void EntryProgressCallback(Entry entry, PingProgressStates state, long msRoundTrip);
|
||||
|
||||
/// <summary>
|
||||
/// An optional delegate to be called whenever an individual entry has changed.
|
||||
/// </summary>
|
||||
public EntryProgressCallback entryProgressCallback;
|
||||
|
||||
/// <summary>
|
||||
/// An optional delegate to be called when the entire process is complete. Called before <seealso cref="DoneEvent"/> is fired.
|
||||
/// </summary>
|
||||
public ActionCompleteDelegate completeCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for posting progress
|
||||
/// </summary>
|
||||
/// <param name="Percent">From 0 to 100 to indicate progress</param>
|
||||
public void UpdateProgress(double Percent)
|
||||
{
|
||||
progressCallback?.Invoke(Percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for posting entry progress
|
||||
/// </summary>
|
||||
/// <param name="entry">The entry being updated</param>
|
||||
/// <param name="state">The new state</param>
|
||||
public void UpdateEntry(Entry entry, PingProgressStates state, long msResponseTime)
|
||||
{
|
||||
entryProgressCallback?.Invoke(entry, state, msResponseTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for posting complete
|
||||
/// </summary>
|
||||
public void Complete()
|
||||
{
|
||||
completeCallback?.Invoke();
|
||||
DoneEvent?.Set();
|
||||
}
|
||||
|
||||
public bool AvoidConnectPortion { get; set; } = false;
|
||||
|
||||
public bool PingFailed { get; set; } = false;
|
||||
}
|
||||
#endregion PingThreadData
|
||||
}
|
||||
|
||||
166
Common/DTS.Common/Utils/SecureQueue.cs
Normal file
166
Common/DTS.Common/Utils/SecureQueue.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using DTS.Common.Utilities.Logging;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class SecureQueue<T> : IDisposable where T : new()
|
||||
{
|
||||
public enum NullPolicy
|
||||
{
|
||||
DenyNull, // throw an exception if try to Enqueue null or zero length
|
||||
SkipNull, // don't complain but don't enqueue null or zero length
|
||||
AllowNull // let null's in too
|
||||
}
|
||||
|
||||
protected Queue<T[]> ByteQueue;
|
||||
protected object ByteQueueLock;
|
||||
protected ManualResetEvent ByteQueueEvent;
|
||||
protected NullPolicy _NullPolicy;
|
||||
protected string Name;
|
||||
protected Stopwatch StopWatch;
|
||||
|
||||
public SecureQueue(NullPolicy policy, string name)
|
||||
{
|
||||
StopWatch = new Stopwatch();
|
||||
StopWatch.Start();
|
||||
ByteQueue = new Queue<T[]>();
|
||||
ByteQueueLock = new object();
|
||||
ByteQueueEvent = new ManualResetEvent(false);
|
||||
_NullPolicy = policy;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void ResetEvent()
|
||||
{
|
||||
//latestReset = GetCaller();
|
||||
ByteQueueEvent.Reset();
|
||||
}
|
||||
|
||||
public void Enqueue(T[] newData)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (ByteQueueLock)
|
||||
{
|
||||
if (newData == null || newData.Length == 0)
|
||||
{
|
||||
switch (_NullPolicy)
|
||||
{
|
||||
case NullPolicy.DenyNull:
|
||||
throw new ArgumentException("SecureQueue_Enqueue_Err1");
|
||||
case NullPolicy.SkipNull:
|
||||
return;
|
||||
}
|
||||
}
|
||||
ByteQueue.Enqueue((T[])newData?.Clone());
|
||||
if (Enums.DASFactory.DFConstantsAndEnums.ExtraCommunicationLogging)
|
||||
{
|
||||
APILogger.Log("Enqueing data", newData);
|
||||
}
|
||||
ByteQueueEvent.Set();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.LogString("SecureQueue.Enqueue: The queue(" + Name + ") threw an exception. " + ex.Message + " " + ex.StackTrace);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for TimeoutMillisec milliseconds for data to arrive in the buffer. Returns false if it times out.
|
||||
/// </summary>
|
||||
/// <param name="timeoutMillisec"></param>
|
||||
/// <returns>true if data has arrived, false if it times out</returns>
|
||||
public bool WaitForData(int timeoutMillisec)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = ByteQueueEvent.WaitOne(timeoutMillisec, false);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.LogString("SecureQueue.WaitForData: The queue(" + Name + ") threw an exception. " + ex.Message + " " + ex.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public WaitHandle QueueWaitHandle => ByteQueueEvent;
|
||||
|
||||
public T[] Dequeue(bool resetEvent)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (ByteQueueLock)
|
||||
{
|
||||
ByteQueueEvent = new ManualResetEvent(false);
|
||||
|
||||
if (ByteQueue.Count == 0)
|
||||
{
|
||||
return new T[0];
|
||||
}
|
||||
if (ByteQueue.Count == 1)
|
||||
{
|
||||
var ret = ByteQueue.Dequeue();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// count how much data we have
|
||||
var totalBytes = ByteQueue.Sum(arr => arr.Length);
|
||||
if (totalBytes == 0)
|
||||
{
|
||||
APILogger.LogString("SecureQueue.Dequeue: The queue(" + Name + ") has 0 bytes!");
|
||||
}
|
||||
// allocate that much
|
||||
var result = new T[totalBytes];
|
||||
var index = 0;
|
||||
// now make one large array
|
||||
while (ByteQueue.Any())
|
||||
{
|
||||
var element = ByteQueue.Dequeue();
|
||||
Array.Copy(element, 0, result, index, element.Length);
|
||||
index += element.Length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.LogString("SecureQueue.Dequeue: The queue(" + Name + ") threw an exception. " + ex.Message + " " + ex.StackTrace);
|
||||
}
|
||||
return new T[0];
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (ByteQueueLock)
|
||||
{
|
||||
//latestFlush = GetCaller();
|
||||
ByteQueueEvent = new ManualResetEvent(false);
|
||||
ByteQueue.Clear();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
APILogger.LogString("SecureQueue.Flush: The queue(" + Name + ") threw an exception. " + ex.Message + " " + ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return ByteQueue.Count == 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Common/DTS.Common/Utils/SerializableDictionary.cs
Normal file
82
Common/DTS.Common/Utils/SerializableDictionary.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace DTS.DASLib.Utility
|
||||
{
|
||||
|
||||
public class XmlDictionary
|
||||
{
|
||||
public SerializableDictionary<string, object> Dictionary { get; set; } = new SerializableDictionary<string, object>();
|
||||
}
|
||||
|
||||
[XmlRoot("dictionary")]
|
||||
public class SerializableDictionary<TKey, TValue>
|
||||
|
||||
: Dictionary<TKey, TValue>, IXmlSerializable
|
||||
{
|
||||
|
||||
#region IXmlSerializable Members
|
||||
|
||||
public System.Xml.Schema.XmlSchema GetSchema()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void ReadXml(System.Xml.XmlReader reader)
|
||||
{
|
||||
var keySerializer = new XmlSerializer(typeof(TKey));
|
||||
var valueSerializer = new XmlSerializer(typeof(TValue));
|
||||
|
||||
var wasEmpty = reader.IsEmptyElement;
|
||||
reader.Read();
|
||||
|
||||
if (wasEmpty)
|
||||
return;
|
||||
reader.ReadStartElement("items");
|
||||
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
|
||||
{
|
||||
reader.ReadStartElement("item");
|
||||
|
||||
reader.ReadStartElement("key");
|
||||
var key = (TKey)keySerializer.Deserialize(reader);
|
||||
reader.ReadEndElement();
|
||||
|
||||
reader.ReadStartElement("value");
|
||||
var value = (TValue)valueSerializer.Deserialize(reader);
|
||||
reader.ReadEndElement();
|
||||
|
||||
Add(key, value);
|
||||
|
||||
reader.ReadEndElement();
|
||||
reader.MoveToContent();
|
||||
}
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(System.Xml.XmlWriter writer)
|
||||
{
|
||||
var keySerializer = new XmlSerializer(typeof(TKey));
|
||||
var valueSerializer = new XmlSerializer(typeof(TValue));
|
||||
writer.WriteStartElement("items");
|
||||
foreach (var key in Keys)
|
||||
{
|
||||
writer.WriteStartElement("item");
|
||||
|
||||
writer.WriteStartElement("key");
|
||||
keySerializer.Serialize(writer, key);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteStartElement("value");
|
||||
var value = this[key];
|
||||
valueSerializer.Serialize(writer, value);
|
||||
writer.WriteEndElement();
|
||||
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
writer.WriteEndElement();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
77
Common/DTS.Common/Utils/StopWatch.cs
Normal file
77
Common/DTS.Common/Utils/StopWatch.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class StopWatchQueue
|
||||
{
|
||||
private readonly Stopwatch _stopWatch;
|
||||
private readonly Queue<long> _watchQueue;
|
||||
private readonly string _name;
|
||||
|
||||
public StopWatchQueue(string name)
|
||||
{
|
||||
_stopWatch = new Stopwatch();
|
||||
_watchQueue = new Queue<long>(5000);
|
||||
_name = name;
|
||||
}
|
||||
|
||||
private static double CalculateStdDev(IEnumerable<double> values)
|
||||
{
|
||||
double ret = 0;
|
||||
|
||||
var valuesArray = values as double[] ?? values.ToArray();
|
||||
if (valuesArray.Length <= 1) return ret;
|
||||
//Compute the Average
|
||||
var avg = valuesArray.Average();
|
||||
|
||||
//Perform the Sum of (value-avg)^2
|
||||
var sum = valuesArray.Sum(d => Math.Pow(d - avg, 2));
|
||||
|
||||
//Put it all together
|
||||
ret = Math.Sqrt(sum / (valuesArray.Length - 1));
|
||||
return ret;
|
||||
}
|
||||
|
||||
~StopWatchQueue()
|
||||
{
|
||||
DumpData();
|
||||
}
|
||||
|
||||
public void DumpData()
|
||||
{
|
||||
if (_watchQueue.Count == 0)
|
||||
return;
|
||||
var qArray = new double[_watchQueue.Count];
|
||||
for (var idx = 0; idx < qArray.Length; idx++)
|
||||
qArray[idx] = Convert.ToDouble(_watchQueue.Dequeue()) / Stopwatch.Frequency * 1000D;
|
||||
var qMax = qArray.Max();
|
||||
var qMin = qArray.Min();
|
||||
var qAvg = qArray.Average();
|
||||
var qStd = CalculateStdDev(qArray);
|
||||
var fName = _name + DateTime.Now.ToFileTime() + ".csv";
|
||||
using (var sw = new StreamWriter(fName, false))
|
||||
{
|
||||
sw.WriteLine("The StopWatchQueue contains {0} entries", qArray.Length);
|
||||
sw.WriteLine("Max={0} Min={1} Average={2} StdDev={3}", qMax, qMin, qAvg, qStd);
|
||||
sw.WriteLine("All values in milli seconds");
|
||||
for (var idx = 0; idx < qArray.Length; idx++)
|
||||
sw.WriteLine("{0},{1}", idx, qArray[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_stopWatch.Start();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_watchQueue.Enqueue(_stopWatch.ElapsedTicks);
|
||||
_stopWatch.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Common/DTS.Common/Utils/TestUtils.cs
Normal file
73
Common/DTS.Common/Utils/TestUtils.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using DTS.Common.Interface;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
//FB 29410 POCO class to be used as parameter for MinUnixTime method
|
||||
public class TestModuleTimeStamp
|
||||
{
|
||||
public int TriggerTimestampSec { get; set; }
|
||||
public int TriggerTimestampNanoSec { get; set; }
|
||||
}
|
||||
public static class TestUtils
|
||||
{
|
||||
// FB15333: Add PTP/RTC timestamp column for CSV exports
|
||||
//FB 29410 refactor the orginal method to use IEnumerable<TestModuleTimeStamp> basemodules as input parameter to be able to use this method in different projects
|
||||
public static Tuple<double, double> MinUnixTime(IEnumerable<TestModuleTimeStamp> basemodules)
|
||||
{
|
||||
if (null != basemodules && basemodules.Any())
|
||||
{
|
||||
var utcAverage = basemodules.Average(module => (double)(module.TriggerTimestampSec + module.TriggerTimestampNanoSec / Common.Constants.NANOS_PER_SECOND));
|
||||
var utcStandardDeviation = basemodules.Select(module => (double)(module.TriggerTimestampSec + module.TriggerTimestampNanoSec / Common.Constants.NANOS_PER_SECOND)).StandardDeviation();
|
||||
utcStandardDeviation = utcStandardDeviation > Common.Constants.TEN_MILLIS_IN_SEC ? Common.Constants.TEN_MILLIS_IN_SEC : utcStandardDeviation; // select min within standard dev or 10ms, whichever is smaller
|
||||
|
||||
var minUnixTime = new Tuple<double, double>(basemodules.First().TriggerTimestampSec, basemodules.First().TriggerTimestampNanoSec);
|
||||
foreach (var module in basemodules)
|
||||
{
|
||||
var moduleTime = (double)(module.TriggerTimestampSec + module.TriggerTimestampNanoSec / Common.Constants.NANOS_PER_SECOND);
|
||||
if (module.TriggerTimestampSec <= minUnixTime.Item1 && module.TriggerTimestampNanoSec < minUnixTime.Item2 &&
|
||||
moduleTime > utcAverage - utcStandardDeviation)
|
||||
{
|
||||
minUnixTime = new Tuple<double, double>(module.TriggerTimestampSec, module.TriggerTimestampNanoSec);
|
||||
}
|
||||
}
|
||||
|
||||
return minUnixTime;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string ParseROISuffix(string test)
|
||||
{
|
||||
var testitems = new string[] { };
|
||||
if (test.Length > 0 && (testitems = test.Split(":".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)).Length > 3)
|
||||
{
|
||||
return testitems.Last();
|
||||
}
|
||||
//FB 18312 Get the ROI of particular event
|
||||
if (test.Length > 0)
|
||||
{
|
||||
var segments = test.Split(":".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
|
||||
var segmentWithEvent = Array.Find(segments, p => p.Contains(DTS.Common.Constants.EventNumber));
|
||||
if (!string.IsNullOrWhiteSpace(segmentWithEvent))
|
||||
{
|
||||
var testSegments = segmentWithEvent.Split('_');
|
||||
if (Array.Exists(testSegments, p => p.Contains(DTS.Common.Constants.EventNumber.Replace("_", ""))))
|
||||
{
|
||||
//found an roi suffix
|
||||
if (!testSegments.Last().Contains(DTS.Common.Constants.EventNumber.Replace("_", "")))
|
||||
{
|
||||
return "_" + testSegments.Last();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
1088
Common/DTS.Common/Utils/Utils.cs
Normal file
1088
Common/DTS.Common/Utils/Utils.cs
Normal file
File diff suppressed because it is too large
Load Diff
22
Common/DTS.Common/Utils/ValueStopWatch.cs
Normal file
22
Common/DTS.Common/Utils/ValueStopWatch.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public readonly struct ValueStopwatch
|
||||
{
|
||||
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
|
||||
private readonly long _startTimestamp;
|
||||
|
||||
private ValueStopwatch(long startTimestamp) => _startTimestamp = startTimestamp;
|
||||
|
||||
public static ValueStopwatch StartNew() => new ValueStopwatch(Stopwatch.GetTimestamp());
|
||||
|
||||
public TimeSpan GetElapsedTime()
|
||||
{
|
||||
var end = Stopwatch.GetTimestamp();
|
||||
var elapsedTicks = (long)((end - _startTimestamp) * TimestampToTicks);
|
||||
return new TimeSpan(elapsedTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Common/DTS.Common/Utils/XMLUtils.cs
Normal file
7
Common/DTS.Common/Utils/XMLUtils.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DTS.Common.Utils
|
||||
{
|
||||
public class XMLUtils
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user