Init RaspberryIO
This commit is contained in:
commit
e7c3f7af91
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
/.vs
|
||||
/Unosquare.Swan.Lite/obj
|
||||
/Unosquare.Swan.Lite/bin
|
||||
/Unosquare.Swan/bin
|
||||
/Unosquare.Swan/obj
|
||||
/Unosquare.RaspberryIO/bin
|
||||
/Unosquare.RaspberryIO/obj
|
37
Unosquare.RaspberryIO.sln
Normal file
37
Unosquare.RaspberryIO.sln
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27703.2026
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unosquare.RaspberryIO", "Unosquare.RaspberryIO\Unosquare.RaspberryIO.csproj", "{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unosquare.Swan", "Unosquare.Swan\Unosquare.Swan.csproj", "{2EA5E3E4-F8C8-4742-8C78-4B070AFCFB73}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unosquare.Swan.Lite", "Unosquare.Swan.Lite\Unosquare.Swan.Lite.csproj", "{AB015683-62E5-47F1-861F-6D037F9C6433}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2EA5E3E4-F8C8-4742-8C78-4B070AFCFB73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2EA5E3E4-F8C8-4742-8C78-4B070AFCFB73}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2EA5E3E4-F8C8-4742-8C78-4B070AFCFB73}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2EA5E3E4-F8C8-4742-8C78-4B070AFCFB73}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{AB015683-62E5-47F1-861F-6D037F9C6433}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AB015683-62E5-47F1-861F-6D037F9C6433}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AB015683-62E5-47F1-861F-6D037F9C6433}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AB015683-62E5-47F1-861F-6D037F9C6433}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2EA74659-FC8F-49F7-894C-1F14BBDEEE8C}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
134
Unosquare.RaspberryIO/Camera/CameraColor.cs
Normal file
134
Unosquare.RaspberryIO/Camera/CameraColor.cs
Normal file
@ -0,0 +1,134 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// A simple RGB color class to represent colors in RGB and YUV colorspaces.
|
||||
/// </summary>
|
||||
public class CameraColor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CameraColor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="r">The red.</param>
|
||||
/// <param name="g">The green.</param>
|
||||
/// <param name="b">The blue.</param>
|
||||
public CameraColor(int r, int g, int b)
|
||||
: this(r, g, b, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CameraColor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="r">The red.</param>
|
||||
/// <param name="g">The green.</param>
|
||||
/// <param name="b">The blue.</param>
|
||||
/// <param name="name">The well-known color name.</param>
|
||||
public CameraColor(int r, int g, int b, string name)
|
||||
{
|
||||
RGB = new[] { Convert.ToByte(r.Clamp(0, 255)), Convert.ToByte(g.Clamp(0, 255)), Convert.ToByte(b.Clamp(0, 255)) };
|
||||
|
||||
var y = (R * .299000f) + (G * .587000f) + (B * .114000f);
|
||||
var u = (R * -.168736f) + (G * -.331264f) + (B * .500000f) + 128f;
|
||||
var v = (R * .500000f) + (G * -.418688f) + (B * -.081312f) + 128f;
|
||||
|
||||
YUV = new byte[] { (byte)y.Clamp(0, 255), (byte)u.Clamp(0, 255), (byte)v.Clamp(0, 255) };
|
||||
Name = name;
|
||||
}
|
||||
|
||||
#region Static Definitions
|
||||
|
||||
/// <summary>
|
||||
/// Gets the predefined white color.
|
||||
/// </summary>
|
||||
public static CameraColor White => new CameraColor(255, 255, 255, nameof(White));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the predefined red color.
|
||||
/// </summary>
|
||||
public static CameraColor Red => new CameraColor(255, 0, 0, nameof(Red));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the predefined green color.
|
||||
/// </summary>
|
||||
public static CameraColor Green => new CameraColor(0, 255, 0, nameof(Green));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the predefined blue color.
|
||||
/// </summary>
|
||||
public static CameraColor Blue => new CameraColor(0, 0, 255, nameof(Blue));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the predefined black color.
|
||||
/// </summary>
|
||||
public static CameraColor Black => new CameraColor(0, 0, 0, nameof(Black));
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the well-known color name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the red byte.
|
||||
/// </summary>
|
||||
public byte R => RGB[0];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the green byte.
|
||||
/// </summary>
|
||||
public byte G => RGB[1];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the blue byte.
|
||||
/// </summary>
|
||||
public byte B => RGB[2];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RGB byte array (3 bytes).
|
||||
/// </summary>
|
||||
public byte[] RGB { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the YUV byte array (3 bytes).
|
||||
/// </summary>
|
||||
public byte[] YUV { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hexadecimal representation of the RGB byte array.
|
||||
/// Preceded by 0x and all in lowercase
|
||||
/// </summary>
|
||||
/// <param name="reverse">if set to <c>true</c> [reverse].</param>
|
||||
/// <returns>A string</returns>
|
||||
public string ToRgbHex(bool reverse)
|
||||
{
|
||||
var data = RGB.ToArray();
|
||||
if (reverse) Array.Reverse(data);
|
||||
return ToHex(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hexadecimal representation of the YUV byte array.
|
||||
/// Preceded by 0x and all in lowercase
|
||||
/// </summary>
|
||||
/// <param name="reverse">if set to <c>true</c> [reverse].</param>
|
||||
/// <returns>A string</returns>
|
||||
public string ToYuvHex(bool reverse)
|
||||
{
|
||||
var data = YUV.ToArray();
|
||||
if (reverse) Array.Reverse(data);
|
||||
return ToHex(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hexadecimal representation of the data byte array
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>A string</returns>
|
||||
private static string ToHex(byte[] data) => $"0x{BitConverter.ToString(data).Replace("-", string.Empty).ToLowerInvariant()}";
|
||||
}
|
||||
}
|
216
Unosquare.RaspberryIO/Camera/CameraController.cs
Normal file
216
Unosquare.RaspberryIO/Camera/CameraController.cs
Normal file
@ -0,0 +1,216 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan.Abstractions;
|
||||
using System;
|
||||
using Swan.Components;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// The Raspberry Pi's camera controller wrapping raspistill and raspivid programs.
|
||||
/// This class is a singleton
|
||||
/// </summary>
|
||||
public class CameraController : SingletonBase<CameraController>
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private static readonly ManualResetEventSlim OperationDone = new ManualResetEventSlim(true);
|
||||
private static readonly object SyncRoot = new object();
|
||||
private static CancellationTokenSource _videoTokenSource = new CancellationTokenSource();
|
||||
private static Task<Task> _videoStreamTask;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the camera module is busy.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is busy; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsBusy => OperationDone.IsSet == false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image Capture Methods
|
||||
|
||||
/// <summary>
|
||||
/// Captures an image asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <param name="ct">The ct.</param>
|
||||
/// <returns>The image bytes</returns>
|
||||
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
|
||||
public async Task<byte[]> CaptureImageAsync(CameraStillSettings settings, CancellationToken ct = default)
|
||||
{
|
||||
if (Instance.IsBusy)
|
||||
throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
|
||||
|
||||
if (settings.CaptureTimeoutMilliseconds <= 0)
|
||||
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than 0");
|
||||
|
||||
try
|
||||
{
|
||||
OperationDone.Reset();
|
||||
|
||||
var output = new MemoryStream();
|
||||
var exitCode = await ProcessRunner.RunProcessAsync(
|
||||
settings.CommandName,
|
||||
settings.CreateProcessArguments(),
|
||||
(data, proc) =>
|
||||
{
|
||||
output.Write(data, 0, data.Length);
|
||||
},
|
||||
null,
|
||||
true,
|
||||
ct);
|
||||
|
||||
return exitCode != 0 ? new byte[] { } : output.ToArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
OperationDone.Set();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures an image.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>The image bytes</returns>
|
||||
public byte[] CaptureImage(CameraStillSettings settings)
|
||||
{
|
||||
return CaptureImageAsync(settings).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a JPEG encoded image asynchronously at 90% quality.
|
||||
/// </summary>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
/// <param name="ct">The ct.</param>
|
||||
/// <returns>The image bytes</returns>
|
||||
public Task<byte[]> CaptureImageJpegAsync(int width, int height, CancellationToken ct = default)
|
||||
{
|
||||
var settings = new CameraStillSettings
|
||||
{
|
||||
CaptureWidth = width,
|
||||
CaptureHeight = height,
|
||||
CaptureJpegQuality = 90,
|
||||
CaptureTimeoutMilliseconds = 300,
|
||||
};
|
||||
|
||||
return CaptureImageAsync(settings, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Captures a JPEG encoded image at 90% quality.
|
||||
/// </summary>
|
||||
/// <param name="width">The width.</param>
|
||||
/// <param name="height">The height.</param>
|
||||
/// <returns>The image bytes</returns>
|
||||
public byte[] CaptureImageJpeg(int width, int height) => CaptureImageJpegAsync(width, height).GetAwaiter().GetResult();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Video Capture Methods
|
||||
|
||||
/// <summary>
|
||||
/// Opens the video stream with a timeout of 0 (running indefinitely) at 1080p resolution, variable bitrate and 25 FPS.
|
||||
/// No preview is shown
|
||||
/// </summary>
|
||||
/// <param name="onDataCallback">The on data callback.</param>
|
||||
/// <param name="onExitCallback">The on exit callback.</param>
|
||||
public void OpenVideoStream(Action<byte[]> onDataCallback, Action onExitCallback = null)
|
||||
{
|
||||
var settings = new CameraVideoSettings
|
||||
{
|
||||
CaptureTimeoutMilliseconds = 0,
|
||||
CaptureDisplayPreview = false,
|
||||
CaptureWidth = 1920,
|
||||
CaptureHeight = 1080
|
||||
};
|
||||
|
||||
OpenVideoStream(settings, onDataCallback, onExitCallback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the video stream with the supplied settings. Capture Timeout Milliseconds has to be 0 or greater
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <param name="onDataCallback">The on data callback.</param>
|
||||
/// <param name="onExitCallback">The on exit callback.</param>
|
||||
/// <exception cref="InvalidOperationException">Cannot use camera module because it is currently busy.</exception>
|
||||
/// <exception cref="ArgumentException">CaptureTimeoutMilliseconds</exception>
|
||||
public void OpenVideoStream(CameraVideoSettings settings, Action<byte[]> onDataCallback, Action onExitCallback)
|
||||
{
|
||||
if (Instance.IsBusy)
|
||||
throw new InvalidOperationException("Cannot use camera module because it is currently busy.");
|
||||
|
||||
if (settings.CaptureTimeoutMilliseconds < 0)
|
||||
throw new ArgumentException($"{nameof(settings.CaptureTimeoutMilliseconds)} needs to be greater than or equal to 0");
|
||||
|
||||
try
|
||||
{
|
||||
OperationDone.Reset();
|
||||
_videoStreamTask = Task.Factory.StartNew(() => VideoWorkerDoWork(settings, onDataCallback, onExitCallback), _videoTokenSource.Token);
|
||||
}
|
||||
catch
|
||||
{
|
||||
OperationDone.Set();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the video stream of a video stream is open.
|
||||
/// </summary>
|
||||
public void CloseVideoStream()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (IsBusy == false)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_videoTokenSource.IsCancellationRequested == false)
|
||||
{
|
||||
_videoTokenSource.Cancel();
|
||||
_videoStreamTask.Wait();
|
||||
}
|
||||
|
||||
_videoTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
private static async Task VideoWorkerDoWork(
|
||||
CameraVideoSettings settings,
|
||||
Action<byte[]> onDataCallback,
|
||||
Action onExitCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessRunner.RunProcessAsync(
|
||||
settings.CommandName,
|
||||
settings.CreateProcessArguments(),
|
||||
(data, proc) => onDataCallback?.Invoke(data),
|
||||
null,
|
||||
true,
|
||||
_videoTokenSource.Token);
|
||||
|
||||
onExitCallback?.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
finally
|
||||
{
|
||||
Instance.CloseVideoStream();
|
||||
OperationDone.Set();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
82
Unosquare.RaspberryIO/Camera/CameraRect.cs
Normal file
82
Unosquare.RaspberryIO/Camera/CameraRect.cs
Normal file
@ -0,0 +1,82 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan;
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Raspberry Pi camera's sensor ROI (Region of Interest)
|
||||
/// </summary>
|
||||
public struct CameraRect
|
||||
{
|
||||
/// <summary>
|
||||
/// The default ROI which is the entire area.
|
||||
/// </summary>
|
||||
public static readonly CameraRect Default = new CameraRect { X = 0M, Y = 0M, W = 1.0M, H = 1.0M };
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the x in relative coordinates. (0.0 to 1.0)
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The x.
|
||||
/// </value>
|
||||
public decimal X { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the y location in relative coordinates. (0.0 to 1.0)
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The y.
|
||||
/// </value>
|
||||
public decimal Y { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width in relative coordinates. (0.0 to 1.0)
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The w.
|
||||
/// </value>
|
||||
public decimal W { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height in relative coordinates. (0.0 to 1.0)
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The h.
|
||||
/// </value>
|
||||
public decimal H { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is equal to the default (The entire area).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is default; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDefault
|
||||
{
|
||||
get
|
||||
{
|
||||
Clamp();
|
||||
return X == Default.X && Y == Default.Y && W == Default.W && H == Default.H;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the members of this ROI to their minimum and maximum values
|
||||
/// </summary>
|
||||
public void Clamp()
|
||||
{
|
||||
X = X.Clamp(0M, 1M);
|
||||
Y = Y.Clamp(0M, 1M);
|
||||
W = W.Clamp(0M, 1M - X);
|
||||
H = H.Clamp(0M, 1M - Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString() => $"{X.ToString(CultureInfo.InvariantCulture)},{Y.ToString(CultureInfo.InvariantCulture)},{W.ToString(CultureInfo.InvariantCulture)},{H.ToString(CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
340
Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs
Normal file
340
Unosquare.RaspberryIO/Camera/CameraSettingsBase.cs
Normal file
@ -0,0 +1,340 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// A base class to implement raspistill and raspivid wrappers
|
||||
/// Full documentation available at
|
||||
/// https://www.raspberrypi.org/documentation/raspbian/applications/camera.md
|
||||
/// </summary>
|
||||
public abstract class CameraSettingsBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The Invariant Culture shorthand
|
||||
/// </summary>
|
||||
protected static readonly CultureInfo Ci = CultureInfo.InvariantCulture;
|
||||
|
||||
#region Capture Settings
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout milliseconds.
|
||||
/// Default value is 5000
|
||||
/// Recommended value is at least 300 in order to let the light collectors open
|
||||
/// </summary>
|
||||
public int CaptureTimeoutMilliseconds { get; set; } = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not to show a preview window on the screen
|
||||
/// </summary>
|
||||
public bool CaptureDisplayPreview { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a preview window is shown in full screen mode if enabled
|
||||
/// </summary>
|
||||
public bool CaptureDisplayPreviewInFullScreen { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether video stabilization should be enabled.
|
||||
/// </summary>
|
||||
public bool CaptureVideoStabilizationEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display preview opacity only if the display preview property is enabled.
|
||||
/// </summary>
|
||||
public byte CaptureDisplayPreviewOpacity { get; set; } = 255;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capture sensor region of interest in relative coordinates.
|
||||
/// </summary>
|
||||
public CameraRect CaptureSensorRoi { get; set; } = CameraRect.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capture shutter speed in microseconds.
|
||||
/// Default -1, Range 0 to 6000000 (equivalent to 6 seconds)
|
||||
/// </summary>
|
||||
public int CaptureShutterSpeedMicroseconds { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exposure mode.
|
||||
/// </summary>
|
||||
public CameraExposureMode CaptureExposure { get; set; } = CameraExposureMode.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture EV compensation. Default is 0, Range is -10 to 10
|
||||
/// Camera exposure compensation is commonly stated in terms of EV units;
|
||||
/// 1 EV is equal to one exposure step (or stop), corresponding to a doubling of exposure.
|
||||
/// Exposure can be adjusted by changing either the lens f-number or the exposure time;
|
||||
/// which one is changed usually depends on the camera's exposure mode.
|
||||
/// </summary>
|
||||
public int CaptureExposureCompensation { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capture metering mode.
|
||||
/// </summary>
|
||||
public CameraMeteringMode CaptureMeteringMode { get; set; } = CameraMeteringMode.Average;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the automatic white balance mode. By default it is set to Auto
|
||||
/// </summary>
|
||||
public CameraWhiteBalanceMode CaptureWhiteBalanceControl { get; set; } = CameraWhiteBalanceMode.Auto;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capture white balance gain on the blue channel. Example: 1.25
|
||||
/// Only takes effect if White balance control is set to off.
|
||||
/// Default is 0
|
||||
/// </summary>
|
||||
public decimal CaptureWhiteBalanceGainBlue { get; set; } = 0M;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the capture white balance gain on the red channel. Example: 1.75
|
||||
/// Only takes effect if White balance control is set to off.
|
||||
/// Default is 0
|
||||
/// </summary>
|
||||
public decimal CaptureWhiteBalanceGainRed { get; set; } = 0M;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dynamic range compensation.
|
||||
/// DRC changes the images by increasing the range of dark areas, and decreasing the brighter areas. This can improve the image in low light areas.
|
||||
/// </summary>
|
||||
public CameraDynamicRangeCompensation CaptureDynamicRangeCompensation { get; set; } =
|
||||
CameraDynamicRangeCompensation.Off;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the picture to take.
|
||||
/// Less than or equal to 0 in either width or height means maximum resolution available.
|
||||
/// </summary>
|
||||
public int CaptureWidth { get; set; } = 640;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the picture to take.
|
||||
/// Less than or equal to 0 in either width or height means maximum resolution available.
|
||||
/// </summary>
|
||||
public int CaptureHeight { get; set; } = 480;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture sharpness. Default is 0, Range form -100 to 100
|
||||
/// </summary>
|
||||
public int ImageSharpness { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture contrast. Default is 0, Range form -100 to 100
|
||||
/// </summary>
|
||||
public int ImageContrast { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture brightness. Default is 50, Range form 0 to 100
|
||||
/// </summary>
|
||||
public int ImageBrightness { get; set; } = 50; // from 0 to 100
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture saturation. Default is 0, Range form -100 to 100
|
||||
/// </summary>
|
||||
public int ImageSaturation { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the picture ISO. Default is -1 Range is 100 to 800
|
||||
/// The higher the value, the more light the sensor absorbs
|
||||
/// </summary>
|
||||
public int ImageIso { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image capture effect to be applied.
|
||||
/// </summary>
|
||||
public CameraImageEffect ImageEffect { get; set; } = CameraImageEffect.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color effect U coordinates.
|
||||
/// Default is -1, Range is 0 to 255
|
||||
/// 128:128 should be effectively a monochrome image.
|
||||
/// </summary>
|
||||
public int ImageColorEffectU { get; set; } = -1; // 0 to 255
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color effect V coordinates.
|
||||
/// Default is -1, Range is 0 to 255
|
||||
/// 128:128 should be effectively a monochrome image.
|
||||
/// </summary>
|
||||
public int ImageColorEffectV { get; set; } = -1; // 0 to 255
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image rotation. Default is no rotation
|
||||
/// </summary>
|
||||
public CameraImageRotation ImageRotation { get; set; } = CameraImageRotation.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the image should be flipped horizontally.
|
||||
/// </summary>
|
||||
public bool ImageFlipHorizontally { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the image should be flipped vertically.
|
||||
/// </summary>
|
||||
public bool ImageFlipVertically { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image annotations using a bitmask (or flags) notation.
|
||||
/// Apply a bitwise OR to the enumeration to include multiple annotations
|
||||
/// </summary>
|
||||
public CameraAnnotation ImageAnnotations { get; set; } = CameraAnnotation.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image annotations text.
|
||||
/// Text may include date/time placeholders by using the '%' character, as used by strftime.
|
||||
/// Example: ABC %Y-%m-%d %X will output ABC 2015-10-28 20:09:33
|
||||
/// </summary>
|
||||
public string ImageAnnotationsText { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the font size of the text annotations
|
||||
/// Default is -1, range is 6 to 160
|
||||
/// </summary>
|
||||
public int ImageAnnotationFontSize { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the color of the text annotations.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The color of the image annotation font.
|
||||
/// </value>
|
||||
public CameraColor ImageAnnotationFontColor { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the background color for text annotations.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The image annotation background.
|
||||
/// </value>
|
||||
public CameraColor ImageAnnotationBackground { get; set; } = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command file executable.
|
||||
/// </summary>
|
||||
public abstract string CommandName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates the process arguments.
|
||||
/// </summary>
|
||||
/// <returns>The string that represents the process arguments</returns>
|
||||
public virtual string CreateProcessArguments()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("-o -"); // output to standard output as opposed to a file.
|
||||
sb.Append($" -t {(CaptureTimeoutMilliseconds < 0 ? "0" : CaptureTimeoutMilliseconds.ToString(Ci))}");
|
||||
|
||||
// Basic Width and height
|
||||
if (CaptureWidth > 0 && CaptureHeight > 0)
|
||||
{
|
||||
sb.Append($" -w {CaptureWidth.ToString(Ci)}");
|
||||
sb.Append($" -h {CaptureHeight.ToString(Ci)}");
|
||||
}
|
||||
|
||||
// Display Preview
|
||||
if (CaptureDisplayPreview)
|
||||
{
|
||||
if (CaptureDisplayPreviewInFullScreen)
|
||||
sb.Append(" -f");
|
||||
|
||||
if (CaptureDisplayPreviewOpacity != byte.MaxValue)
|
||||
sb.Append($" -op {CaptureDisplayPreviewOpacity.ToString(Ci)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(" -n"); // no preview
|
||||
}
|
||||
|
||||
// Picture Settings
|
||||
if (ImageSharpness != 0)
|
||||
sb.Append($" -sh {ImageSharpness.Clamp(-100, 100).ToString(Ci)}");
|
||||
|
||||
if (ImageContrast != 0)
|
||||
sb.Append($" -co {ImageContrast.Clamp(-100, 100).ToString(Ci)}");
|
||||
|
||||
if (ImageBrightness != 50)
|
||||
sb.Append($" -br {ImageBrightness.Clamp(0, 100).ToString(Ci)}");
|
||||
|
||||
if (ImageSaturation != 0)
|
||||
sb.Append($" -sa {ImageSaturation.Clamp(-100, 100).ToString(Ci)}");
|
||||
|
||||
if (ImageIso >= 100)
|
||||
sb.Append($" -ISO {ImageIso.Clamp(100, 800).ToString(Ci)}");
|
||||
|
||||
if (CaptureVideoStabilizationEnabled)
|
||||
sb.Append(" -vs");
|
||||
|
||||
if (CaptureExposureCompensation != 0)
|
||||
sb.Append($" -ev {CaptureExposureCompensation.Clamp(-10, 10).ToString(Ci)}");
|
||||
|
||||
if (CaptureExposure != CameraExposureMode.Auto)
|
||||
sb.Append($" -ex {CaptureExposure.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (CaptureWhiteBalanceControl != CameraWhiteBalanceMode.Auto)
|
||||
sb.Append($" -awb {CaptureWhiteBalanceControl.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (ImageEffect != CameraImageEffect.None)
|
||||
sb.Append($" -ifx {ImageEffect.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (ImageColorEffectU >= 0 && ImageColorEffectV >= 0)
|
||||
{
|
||||
sb.Append(
|
||||
$" -cfx {ImageColorEffectU.Clamp(0, 255).ToString(Ci)}:{ImageColorEffectV.Clamp(0, 255).ToString(Ci)}");
|
||||
}
|
||||
|
||||
if (CaptureMeteringMode != CameraMeteringMode.Average)
|
||||
sb.Append($" -mm {CaptureMeteringMode.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (ImageRotation != CameraImageRotation.None)
|
||||
sb.Append($" -rot {((int)ImageRotation).ToString(Ci)}");
|
||||
|
||||
if (ImageFlipHorizontally)
|
||||
sb.Append(" -hf");
|
||||
|
||||
if (ImageFlipVertically)
|
||||
sb.Append(" -vf");
|
||||
|
||||
if (CaptureSensorRoi.IsDefault == false)
|
||||
sb.Append($" -roi {CaptureSensorRoi}");
|
||||
|
||||
if (CaptureShutterSpeedMicroseconds > 0)
|
||||
sb.Append($" -ss {CaptureShutterSpeedMicroseconds.Clamp(0, 6000000).ToString(Ci)}");
|
||||
|
||||
if (CaptureDynamicRangeCompensation != CameraDynamicRangeCompensation.Off)
|
||||
sb.Append($" -drc {CaptureDynamicRangeCompensation.ToString().ToLowerInvariant()}");
|
||||
|
||||
if (CaptureWhiteBalanceControl == CameraWhiteBalanceMode.Off &&
|
||||
(CaptureWhiteBalanceGainBlue != 0M || CaptureWhiteBalanceGainRed != 0M))
|
||||
sb.Append($" -awbg {CaptureWhiteBalanceGainBlue.ToString(Ci)},{CaptureWhiteBalanceGainRed.ToString(Ci)}");
|
||||
|
||||
if (ImageAnnotationFontSize > 0)
|
||||
{
|
||||
sb.Append($" -ae {ImageAnnotationFontSize.Clamp(6, 160).ToString(Ci)}");
|
||||
sb.Append($",{(ImageAnnotationFontColor == null ? "0xff" : ImageAnnotationFontColor.ToYuvHex(true))}");
|
||||
|
||||
if (ImageAnnotationBackground != null)
|
||||
{
|
||||
ImageAnnotations |= CameraAnnotation.SolidBackground;
|
||||
sb.Append($",{ImageAnnotationBackground.ToYuvHex(true)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (ImageAnnotations != CameraAnnotation.None)
|
||||
sb.Append($" -a {((int)ImageAnnotations).ToString(Ci)}");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ImageAnnotationsText) == false)
|
||||
sb.Append($" -a \"{ImageAnnotationsText.Replace("\"", "'")}\"");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
120
Unosquare.RaspberryIO/Camera/CameraStillSettings.cs
Normal file
120
Unosquare.RaspberryIO/Camera/CameraStillSettings.cs
Normal file
@ -0,0 +1,120 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a wrapper for the raspistill program and its settings (command-line arguments)
|
||||
/// </summary>
|
||||
/// <seealso cref="CameraSettingsBase" />
|
||||
public class CameraStillSettings : CameraSettingsBase
|
||||
{
|
||||
private int _rotate;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string CommandName => "raspistill";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the preview window (if enabled) uses native capture resolution
|
||||
/// This may slow down preview FPS
|
||||
/// </summary>
|
||||
public bool CaptureDisplayPreviewAtResolution { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the encoding format the hardware will use for the output.
|
||||
/// </summary>
|
||||
public CameraImageEncodingFormat CaptureEncoding { get; set; } = CameraImageEncodingFormat.Jpg;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the quality for JPEG only encoding mode.
|
||||
/// Value ranges from 0 to 100
|
||||
/// </summary>
|
||||
public int CaptureJpegQuality { get; set; } = 90;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the JPEG encoder should add raw bayer metadata.
|
||||
/// </summary>
|
||||
public bool CaptureJpegIncludeRawBayerMetadata { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// JPEG EXIF data
|
||||
/// Keys and values must be already properly escaped. Otherwise the command will fail.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> CaptureJpegExtendedInfo { get; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [horizontal flip].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [horizontal flip]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool HorizontalFlip { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [vertical flip].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [vertical flip]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool VerticalFlip { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the rotation.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Valid range 0-359</exception>
|
||||
public int Rotation
|
||||
{
|
||||
get => _rotate;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > 359)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), "Valid range 0-359");
|
||||
}
|
||||
|
||||
_rotate = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string CreateProcessArguments()
|
||||
{
|
||||
var sb = new StringBuilder(base.CreateProcessArguments());
|
||||
sb.Append($" -e {CaptureEncoding.ToString().ToLowerInvariant()}");
|
||||
|
||||
// JPEG Encoder specific arguments
|
||||
if (CaptureEncoding == CameraImageEncodingFormat.Jpg)
|
||||
{
|
||||
sb.Append($" -q {CaptureJpegQuality.Clamp(0, 100).ToString(Ci)}");
|
||||
|
||||
if (CaptureJpegIncludeRawBayerMetadata)
|
||||
sb.Append(" -r");
|
||||
|
||||
// JPEG EXIF data
|
||||
if (CaptureJpegExtendedInfo.Count > 0)
|
||||
{
|
||||
foreach (var kvp in CaptureJpegExtendedInfo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(kvp.Key) || string.IsNullOrWhiteSpace(kvp.Value))
|
||||
continue;
|
||||
|
||||
sb.Append($" -x \"{kvp.Key.Replace("\"", "'")}={kvp.Value.Replace("\"", "'")}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display preview settings
|
||||
if (CaptureDisplayPreview && CaptureDisplayPreviewAtResolution) sb.Append(" -fp");
|
||||
|
||||
if (Rotation != 0) sb.Append($" -rot {Rotation}");
|
||||
|
||||
if (HorizontalFlip) sb.Append(" -hf");
|
||||
|
||||
if (VerticalFlip) sb.Append(" -vf");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
94
Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs
Normal file
94
Unosquare.RaspberryIO/Camera/CameraVideoSettings.cs
Normal file
@ -0,0 +1,94 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using Swan;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the raspivid camera settings for video capture functionality
|
||||
/// </summary>
|
||||
/// <seealso cref="CameraSettingsBase" />
|
||||
public class CameraVideoSettings : CameraSettingsBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string CommandName => "raspivid";
|
||||
|
||||
/// <summary>
|
||||
/// Use bits per second, so 10Mbits/s would be -b 10000000. For H264, 1080p30 a high quality bitrate would be 15Mbits/s or more.
|
||||
/// Maximum bitrate is 25Mbits/s (-b 25000000), but much over 17Mbits/s won't show noticeable improvement at 1080p30.
|
||||
/// Default -1
|
||||
/// </summary>
|
||||
public int CaptureBitrate { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the framerate.
|
||||
/// Default 25, range 2 to 30
|
||||
/// </summary>
|
||||
public int CaptureFramerate { get; set; } = 25;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the intra refresh period (GoP) rate for the recorded video. H264 video uses a complete frame (I-frame) every intra
|
||||
/// refresh period, from which subsequent frames are based. This option specifies the number of frames between each I-frame.
|
||||
/// Larger numbers here will reduce the size of the resulting video, and smaller numbers make the stream less error-prone.
|
||||
/// </summary>
|
||||
public int CaptureKeyframeRate { get; set; } = 25;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the initial quantisation parameter for the stream. Varies from approximately 10 to 40, and will greatly affect
|
||||
/// the quality of the recording. Higher values reduce quality and decrease file size. Combine this setting with a
|
||||
/// bitrate of 0 to set a completely variable bitrate.
|
||||
/// </summary>
|
||||
public int CaptureQuantisation { get; set; } = 23;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the profile.
|
||||
/// Sets the H264 profile to be used for the encoding.
|
||||
/// Default is Main mode
|
||||
/// </summary>
|
||||
public CameraH264Profile CaptureProfile { get; set; } = CameraH264Profile.Main;
|
||||
|
||||
/// <summary>
|
||||
/// Forces the stream to include PPS and SPS headers on every I-frame. Needed for certain streaming cases
|
||||
/// e.g. Apple HLS. These headers are small, so don't greatly increase the file size.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [interleave headers]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaptureInterleaveHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Switch on an option to display the preview after compression. This will show any compression artefacts in the preview window. In normal operation,
|
||||
/// the preview will show the camera output prior to being compressed. This option is not guaranteed to work in future releases.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [capture display preview encoded]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaptureDisplayPreviewEncoded { get; set; } = false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string CreateProcessArguments()
|
||||
{
|
||||
var sb = new StringBuilder(base.CreateProcessArguments());
|
||||
|
||||
sb.Append($" -pf {CaptureProfile.ToString().ToLowerInvariant()}");
|
||||
if (CaptureBitrate < 0)
|
||||
sb.Append($" -b {CaptureBitrate.Clamp(0, 25000000).ToString(Ci)}");
|
||||
|
||||
if (CaptureFramerate >= 2)
|
||||
sb.Append($" -fps {CaptureFramerate.Clamp(2, 30).ToString(Ci)}");
|
||||
|
||||
if (CaptureDisplayPreview && CaptureDisplayPreviewEncoded)
|
||||
sb.Append(" -e");
|
||||
|
||||
if (CaptureKeyframeRate > 0)
|
||||
sb.Append($" -g {CaptureKeyframeRate.ToString(Ci)}");
|
||||
|
||||
if (CaptureQuantisation >= 0)
|
||||
sb.Append($" -qp {CaptureQuantisation.Clamp(0, 40).ToString(Ci)}");
|
||||
|
||||
if (CaptureInterleaveHeaders)
|
||||
sb.Append(" -ih");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
423
Unosquare.RaspberryIO/Camera/Enums.cs
Normal file
423
Unosquare.RaspberryIO/Camera/Enums.cs
Normal file
@ -0,0 +1,423 @@
|
||||
namespace Unosquare.RaspberryIO.Camera
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the available encoding formats for the Raspberry Pi camera module
|
||||
/// </summary>
|
||||
public enum CameraImageEncodingFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// The JPG
|
||||
/// </summary>
|
||||
Jpg,
|
||||
|
||||
/// <summary>
|
||||
/// The BMP
|
||||
/// </summary>
|
||||
Bmp,
|
||||
|
||||
/// <summary>
|
||||
/// The GIF
|
||||
/// </summary>
|
||||
Gif,
|
||||
|
||||
/// <summary>
|
||||
/// The PNG
|
||||
/// </summary>
|
||||
Png,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different exposure modes for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
public enum CameraExposureMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The automatic
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// The night
|
||||
/// </summary>
|
||||
Night,
|
||||
|
||||
/// <summary>
|
||||
/// The night preview
|
||||
/// </summary>
|
||||
NightPreview,
|
||||
|
||||
/// <summary>
|
||||
/// The backlight
|
||||
/// </summary>
|
||||
Backlight,
|
||||
|
||||
/// <summary>
|
||||
/// The spotlight
|
||||
/// </summary>
|
||||
Spotlight,
|
||||
|
||||
/// <summary>
|
||||
/// The sports
|
||||
/// </summary>
|
||||
Sports,
|
||||
|
||||
/// <summary>
|
||||
/// The snow
|
||||
/// </summary>
|
||||
Snow,
|
||||
|
||||
/// <summary>
|
||||
/// The beach
|
||||
/// </summary>
|
||||
Beach,
|
||||
|
||||
/// <summary>
|
||||
/// The very long
|
||||
/// </summary>
|
||||
VeryLong,
|
||||
|
||||
/// <summary>
|
||||
/// The fixed FPS
|
||||
/// </summary>
|
||||
FixedFps,
|
||||
|
||||
/// <summary>
|
||||
/// The anti shake
|
||||
/// </summary>
|
||||
AntiShake,
|
||||
|
||||
/// <summary>
|
||||
/// The fireworks
|
||||
/// </summary>
|
||||
Fireworks
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different AWB (Auto White Balance) modes for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
public enum CameraWhiteBalanceMode
|
||||
{
|
||||
/// <summary>
|
||||
/// No white balance
|
||||
/// </summary>
|
||||
Off,
|
||||
|
||||
/// <summary>
|
||||
/// The automatic
|
||||
/// </summary>
|
||||
Auto,
|
||||
|
||||
/// <summary>
|
||||
/// The sun
|
||||
/// </summary>
|
||||
Sun,
|
||||
|
||||
/// <summary>
|
||||
/// The cloud
|
||||
/// </summary>
|
||||
Cloud,
|
||||
|
||||
/// <summary>
|
||||
/// The shade
|
||||
/// </summary>
|
||||
Shade,
|
||||
|
||||
/// <summary>
|
||||
/// The tungsten
|
||||
/// </summary>
|
||||
Tungsten,
|
||||
|
||||
/// <summary>
|
||||
/// The fluorescent
|
||||
/// </summary>
|
||||
Fluorescent,
|
||||
|
||||
/// <summary>
|
||||
/// The incandescent
|
||||
/// </summary>
|
||||
Incandescent,
|
||||
|
||||
/// <summary>
|
||||
/// The flash
|
||||
/// </summary>
|
||||
Flash,
|
||||
|
||||
/// <summary>
|
||||
/// The horizon
|
||||
/// </summary>
|
||||
Horizon
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the available image effects for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
public enum CameraImageEffect
|
||||
{
|
||||
/// <summary>
|
||||
/// No effect
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The negative
|
||||
/// </summary>
|
||||
Negative,
|
||||
|
||||
/// <summary>
|
||||
/// The solarise
|
||||
/// </summary>
|
||||
Solarise,
|
||||
|
||||
/// <summary>
|
||||
/// The whiteboard
|
||||
/// </summary>
|
||||
Whiteboard,
|
||||
|
||||
/// <summary>
|
||||
/// The blackboard
|
||||
/// </summary>
|
||||
Blackboard,
|
||||
|
||||
/// <summary>
|
||||
/// The sketch
|
||||
/// </summary>
|
||||
Sketch,
|
||||
|
||||
/// <summary>
|
||||
/// The denoise
|
||||
/// </summary>
|
||||
Denoise,
|
||||
|
||||
/// <summary>
|
||||
/// The emboss
|
||||
/// </summary>
|
||||
Emboss,
|
||||
|
||||
/// <summary>
|
||||
/// The oil paint
|
||||
/// </summary>
|
||||
OilPaint,
|
||||
|
||||
/// <summary>
|
||||
/// The hatch
|
||||
/// </summary>
|
||||
Hatch,
|
||||
|
||||
/// <summary>
|
||||
/// Graphite Pen
|
||||
/// </summary>
|
||||
GPen,
|
||||
|
||||
/// <summary>
|
||||
/// The pastel
|
||||
/// </summary>
|
||||
Pastel,
|
||||
|
||||
/// <summary>
|
||||
/// The water colour
|
||||
/// </summary>
|
||||
WaterColour,
|
||||
|
||||
/// <summary>
|
||||
/// The film
|
||||
/// </summary>
|
||||
Film,
|
||||
|
||||
/// <summary>
|
||||
/// The blur
|
||||
/// </summary>
|
||||
Blur,
|
||||
|
||||
/// <summary>
|
||||
/// The saturation
|
||||
/// </summary>
|
||||
Saturation,
|
||||
|
||||
/// <summary>
|
||||
/// The solour swap
|
||||
/// </summary>
|
||||
SolourSwap,
|
||||
|
||||
/// <summary>
|
||||
/// The washed out
|
||||
/// </summary>
|
||||
WashedOut,
|
||||
|
||||
/// <summary>
|
||||
/// The colour point
|
||||
/// </summary>
|
||||
ColourPoint,
|
||||
|
||||
/// <summary>
|
||||
/// The colour balance
|
||||
/// </summary>
|
||||
ColourBalance,
|
||||
|
||||
/// <summary>
|
||||
/// The cartoon
|
||||
/// </summary>
|
||||
Cartoon
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different metering modes for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
public enum CameraMeteringMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The average
|
||||
/// </summary>
|
||||
Average,
|
||||
|
||||
/// <summary>
|
||||
/// The spot
|
||||
/// </summary>
|
||||
Spot,
|
||||
|
||||
/// <summary>
|
||||
/// The backlit
|
||||
/// </summary>
|
||||
Backlit,
|
||||
|
||||
/// <summary>
|
||||
/// The matrix
|
||||
/// </summary>
|
||||
Matrix,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different image rotation modes for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
public enum CameraImageRotation
|
||||
{
|
||||
/// <summary>
|
||||
/// No rerotation
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 90 Degrees
|
||||
/// </summary>
|
||||
Degrees90 = 90,
|
||||
|
||||
/// <summary>
|
||||
/// 180 Degrees
|
||||
/// </summary>
|
||||
Degrees180 = 180,
|
||||
|
||||
/// <summary>
|
||||
/// 270 degrees
|
||||
/// </summary>
|
||||
Degrees270 = 270
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different DRC (Dynamic Range Compensation) modes for the Raspberry Pi's camera module
|
||||
/// Helpful for low light photos
|
||||
/// </summary>
|
||||
public enum CameraDynamicRangeCompensation
|
||||
{
|
||||
/// <summary>
|
||||
/// The off setting
|
||||
/// </summary>
|
||||
Off,
|
||||
|
||||
/// <summary>
|
||||
/// The low
|
||||
/// </summary>
|
||||
Low,
|
||||
|
||||
/// <summary>
|
||||
/// The medium
|
||||
/// </summary>
|
||||
Medium,
|
||||
|
||||
/// <summary>
|
||||
/// The high
|
||||
/// </summary>
|
||||
High
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the bit-wise mask flags for the available annotation elements for the Raspberry Pi's camera module
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CameraAnnotation
|
||||
{
|
||||
/// <summary>
|
||||
/// The none
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The time
|
||||
/// </summary>
|
||||
Time = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The date
|
||||
/// </summary>
|
||||
Date = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The shutter settings
|
||||
/// </summary>
|
||||
ShutterSettings = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The caf settings
|
||||
/// </summary>
|
||||
CafSettings = 32,
|
||||
|
||||
/// <summary>
|
||||
/// The gain settings
|
||||
/// </summary>
|
||||
GainSettings = 64,
|
||||
|
||||
/// <summary>
|
||||
/// The lens settings
|
||||
/// </summary>
|
||||
LensSettings = 128,
|
||||
|
||||
/// <summary>
|
||||
/// The motion settings
|
||||
/// </summary>
|
||||
MotionSettings = 256,
|
||||
|
||||
/// <summary>
|
||||
/// The frame number
|
||||
/// </summary>
|
||||
FrameNumber = 512,
|
||||
|
||||
/// <summary>
|
||||
/// The solid background
|
||||
/// </summary>
|
||||
SolidBackground = 1024,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different H.264 encoding profiles to be used when capturing video.
|
||||
/// </summary>
|
||||
public enum CameraH264Profile
|
||||
{
|
||||
/// <summary>
|
||||
/// BP: Primarily for lower-cost applications with limited computing resources,
|
||||
/// this profile is used widely in videoconferencing and mobile applications.
|
||||
/// </summary>
|
||||
Baseline,
|
||||
|
||||
/// <summary>
|
||||
/// MP: Originally intended as the mainstream consumer profile for broadcast
|
||||
/// and storage applications, the importance of this profile faded when the High profile was developed for those applications.
|
||||
/// </summary>
|
||||
Main,
|
||||
|
||||
/// <summary>
|
||||
/// HiP: The primary profile for broadcast and disc storage applications, particularly
|
||||
/// for high-definition television applications (this is the profile adopted into HD DVD and Blu-ray Disc, for example).
|
||||
/// </summary>
|
||||
High
|
||||
}
|
||||
}
|
80
Unosquare.RaspberryIO/Computer/DsiDisplay.cs
Normal file
80
Unosquare.RaspberryIO/Computer/DsiDisplay.cs
Normal file
@ -0,0 +1,80 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
using Swan.Abstractions;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// The Official Raspberry Pi 7-inch touch display from the foundation
|
||||
/// Some docs available here:
|
||||
/// http://forums.pimoroni.com/t/official-7-raspberry-pi-touch-screen-faq/959
|
||||
/// </summary>
|
||||
public class DsiDisplay : SingletonBase<DsiDisplay>
|
||||
{
|
||||
private const string BacklightFilename = "/sys/class/backlight/rpi_backlight/bl_power";
|
||||
private const string BrightnessFilename = "/sys/class/backlight/rpi_backlight/brightness";
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="DsiDisplay"/> class from being created.
|
||||
/// </summary>
|
||||
private DsiDisplay()
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Pi Foundation Display files are present.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is present; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsPresent => File.Exists(BrightnessFilename);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the brightness of the DSI display via filesystem.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The brightness.
|
||||
/// </value>
|
||||
public byte Brightness
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPresent == false) return 0;
|
||||
|
||||
return byte.TryParse(File.ReadAllText(BrightnessFilename).Trim(), out var brightness) ? brightness : (byte)0;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (IsPresent == false) return;
|
||||
File.WriteAllText(BrightnessFilename, value.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the backlight of the DSI display on.
|
||||
/// This operation is performed via the file system
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is backlight on; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsBacklightOn
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPresent == false) return false;
|
||||
|
||||
if (int.TryParse(File.ReadAllText(BacklightFilename).Trim(), out var backlight))
|
||||
return backlight == 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (IsPresent == false) return;
|
||||
|
||||
File.WriteAllText(BacklightFilename, value ? "0" : "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
40
Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs
Normal file
40
Unosquare.RaspberryIO/Computer/NetworkAdapterInfo.cs
Normal file
@ -0,0 +1,40 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
using System.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Network Adapter
|
||||
/// </summary>
|
||||
public class NetworkAdapterInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IP V4 address.
|
||||
/// </summary>
|
||||
public IPAddress IPv4 { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IP V6 address.
|
||||
/// </summary>
|
||||
public IPAddress IPv6 { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the access point.
|
||||
/// </summary>
|
||||
public string AccessPointName { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MAC (Physical) address.
|
||||
/// </summary>
|
||||
public string MacAddress { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is wireless.
|
||||
/// </summary>
|
||||
public bool IsWireless { get; internal set; }
|
||||
}
|
||||
}
|
266
Unosquare.RaspberryIO/Computer/NetworkSettings.cs
Normal file
266
Unosquare.RaspberryIO/Computer/NetworkSettings.cs
Normal file
@ -0,0 +1,266 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
using Swan;
|
||||
using Swan.Abstractions;
|
||||
using Swan.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the network information
|
||||
/// </summary>
|
||||
public class NetworkSettings : SingletonBase<NetworkSettings>
|
||||
{
|
||||
private const string EssidTag = "ESSID:";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local machine Host Name.
|
||||
/// </summary>
|
||||
public string HostName => Network.HostName;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the wireless networks.
|
||||
/// </summary>
|
||||
/// <param name="adapter">The adapter.</param>
|
||||
/// <returns>A list of WiFi networks</returns>
|
||||
public List<WirelessNetworkInfo> RetrieveWirelessNetworks(string adapter) => RetrieveWirelessNetworks(new[] { adapter });
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the wireless networks.
|
||||
/// </summary>
|
||||
/// <param name="adapters">The adapters.</param>
|
||||
/// <returns>A list of WiFi networks</returns>
|
||||
public List<WirelessNetworkInfo> RetrieveWirelessNetworks(string[] adapters = null)
|
||||
{
|
||||
var result = new List<WirelessNetworkInfo>();
|
||||
|
||||
foreach (var networkAdapter in adapters ?? RetrieveAdapters().Where(x => x.IsWireless).Select(x => x.Name))
|
||||
{
|
||||
var wirelessOutput = ProcessRunner.GetProcessOutputAsync("iwlist", $"{networkAdapter} scanning").Result;
|
||||
var outputLines =
|
||||
wirelessOutput.Split('\n')
|
||||
.Select(x => x.Trim())
|
||||
.Where(x => string.IsNullOrWhiteSpace(x) == false)
|
||||
.ToArray();
|
||||
|
||||
for (var i = 0; i < outputLines.Length; i++)
|
||||
{
|
||||
var line = outputLines[i];
|
||||
|
||||
if (line.StartsWith(EssidTag) == false) continue;
|
||||
|
||||
var network = new WirelessNetworkInfo()
|
||||
{
|
||||
Name = line.Replace(EssidTag, string.Empty).Replace("\"", string.Empty)
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (i + 1 >= outputLines.Length) break;
|
||||
|
||||
// should look for two lines before the ESSID acording to the scan
|
||||
line = outputLines[i - 2];
|
||||
|
||||
if (line.StartsWith("Quality="))
|
||||
{
|
||||
network.Quality = line.Replace("Quality=", string.Empty);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (i + 1 >= outputLines.Length) break;
|
||||
|
||||
// should look for a line before the ESSID acording to the scan
|
||||
line = outputLines[i - 1];
|
||||
|
||||
if (line.StartsWith("Encryption key:"))
|
||||
{
|
||||
network.IsEncrypted = line.Replace("Encryption key:", string.Empty).Trim() == "on";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Any(x => x.Name == network.Name) == false)
|
||||
result.Add(network);
|
||||
}
|
||||
}
|
||||
|
||||
return result.OrderBy(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups the wireless network.
|
||||
/// </summary>
|
||||
/// <param name="adapterName">Name of the adapter.</param>
|
||||
/// <param name="networkSsid">The network ssid.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <param name="countryCode">The 2-letter country code in uppercase. Default is US.</param>
|
||||
/// <returns>True if successful. Otherwise, false.</returns>
|
||||
public bool SetupWirelessNetwork(string adapterName, string networkSsid, string password = null, string countryCode = "US")
|
||||
{
|
||||
// TODO: Get the country where the device is located to set 'country' param in payload var
|
||||
var payload = $"country={countryCode}\nctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\nupdate_config=1\n";
|
||||
payload += string.IsNullOrEmpty(password)
|
||||
? $"network={{\n\tssid=\"{networkSsid}\"\n\t}}\n"
|
||||
: $"network={{\n\tssid=\"{networkSsid}\"\n\tpsk=\"{password}\"\n\t}}\n";
|
||||
try
|
||||
{
|
||||
File.WriteAllText("/etc/wpa_supplicant/wpa_supplicant.conf", payload);
|
||||
ProcessRunner.GetProcessOutputAsync("pkill", "-f wpa_supplicant").Wait();
|
||||
ProcessRunner.GetProcessOutputAsync("ifdown", adapterName).Wait();
|
||||
ProcessRunner.GetProcessOutputAsync("ifup", adapterName).Wait();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.Log(nameof(NetworkSettings));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the network adapters.
|
||||
/// </summary>
|
||||
/// <returns>A list of network adapters.</returns>
|
||||
public List<NetworkAdapterInfo> RetrieveAdapters()
|
||||
{
|
||||
const string hWaddr = "HWaddr ";
|
||||
const string ether = "ether ";
|
||||
|
||||
var result = new List<NetworkAdapterInfo>();
|
||||
var interfacesOutput = ProcessRunner.GetProcessOutputAsync("ifconfig").Result;
|
||||
var wlanOutput = ProcessRunner.GetProcessOutputAsync("iwconfig")
|
||||
.Result.Split('\n')
|
||||
.Where(x => x.Contains("no wireless extensions.") == false)
|
||||
.ToArray();
|
||||
|
||||
var outputLines = interfacesOutput.Split('\n').Where(x => string.IsNullOrWhiteSpace(x) == false).ToArray();
|
||||
|
||||
for (var i = 0; i < outputLines.Length; i++)
|
||||
{
|
||||
// grab the current line
|
||||
var line = outputLines[i];
|
||||
|
||||
// skip if the line is indented
|
||||
if (char.IsLetterOrDigit(line[0]) == false)
|
||||
continue;
|
||||
|
||||
// Read the line as an adatper
|
||||
var adapter = new NetworkAdapterInfo
|
||||
{
|
||||
Name = line.Substring(0, line.IndexOf(' ')).TrimEnd(':')
|
||||
};
|
||||
|
||||
// Parse the MAC address in old version of ifconfig; it comes in the first line
|
||||
if (line.IndexOf(hWaddr) >= 0)
|
||||
{
|
||||
var startIndexHwd = line.IndexOf(hWaddr) + hWaddr.Length;
|
||||
adapter.MacAddress = line.Substring(startIndexHwd, 17).Trim();
|
||||
}
|
||||
|
||||
// Parse the info in lines other than the first
|
||||
for (var j = i + 1; j < outputLines.Length; j++)
|
||||
{
|
||||
// Get the contents of the indented line
|
||||
var indentedLine = outputLines[j];
|
||||
|
||||
// We have hit the next adapter info
|
||||
if (char.IsLetterOrDigit(indentedLine[0]))
|
||||
{
|
||||
i = j - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse the MAC address in new versions of ifconfig; it no longer comes in the first line
|
||||
if (indentedLine.IndexOf(ether) >= 0 && string.IsNullOrWhiteSpace(adapter.MacAddress))
|
||||
{
|
||||
var startIndexHwd = indentedLine.IndexOf(ether) + ether.Length;
|
||||
adapter.MacAddress = indentedLine.Substring(startIndexHwd, 17).Trim();
|
||||
}
|
||||
|
||||
// Parse the IPv4 Address
|
||||
{
|
||||
var addressText = ParseOutputTagFromLine(indentedLine, "inet addr:") ?? ParseOutputTagFromLine(indentedLine, "inet ");
|
||||
|
||||
if (addressText != null)
|
||||
{
|
||||
if (IPAddress.TryParse(addressText, out var outValue))
|
||||
adapter.IPv4 = outValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the IPv6 Address
|
||||
{
|
||||
var addressText = ParseOutputTagFromLine(indentedLine, "inet6 addr:") ?? ParseOutputTagFromLine(indentedLine, "inet6 ");
|
||||
|
||||
if (addressText != null)
|
||||
{
|
||||
if (IPAddress.TryParse(addressText, out var outValue))
|
||||
adapter.IPv6 = outValue;
|
||||
}
|
||||
}
|
||||
|
||||
// we have hit the end of the output in an indented line
|
||||
if (j >= outputLines.Length - 1)
|
||||
i = outputLines.Length;
|
||||
}
|
||||
|
||||
// Retrieve the wireless LAN info
|
||||
var wlanInfo = wlanOutput.FirstOrDefault(x => x.StartsWith(adapter.Name));
|
||||
|
||||
if (wlanInfo != null)
|
||||
{
|
||||
adapter.IsWireless = true;
|
||||
var essidParts = wlanInfo.Split(new[] { EssidTag }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (essidParts.Length >= 2)
|
||||
{
|
||||
adapter.AccessPointName = essidParts[1].Replace("\"", string.Empty).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Add the current adapter to the result
|
||||
result.Add(adapter);
|
||||
}
|
||||
|
||||
return result.OrderBy(x => x.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves current wireless connected network name.
|
||||
/// </summary>
|
||||
/// <returns>The connected network name.</returns>
|
||||
public string GetWirelessNetworkName() => ProcessRunner.GetProcessOutputAsync("iwgetid", "-r").Result;
|
||||
|
||||
/// <summary>
|
||||
/// Parses the output tag from the given line.
|
||||
/// </summary>
|
||||
/// <param name="indentedLine">The indented line.</param>
|
||||
/// <param name="tagName">Name of the tag.</param>
|
||||
/// <returns>The value after the tag identifier</returns>
|
||||
private static string ParseOutputTagFromLine(string indentedLine, string tagName)
|
||||
{
|
||||
if (indentedLine.IndexOf(tagName) < 0)
|
||||
return null;
|
||||
|
||||
var startIndex = indentedLine.IndexOf(tagName) + tagName.Length;
|
||||
var builder = new StringBuilder(1024);
|
||||
for (var c = startIndex; c < indentedLine.Length; c++)
|
||||
{
|
||||
var currentChar = indentedLine[c];
|
||||
if (!char.IsPunctuation(currentChar) && !char.IsLetterOrDigit(currentChar))
|
||||
break;
|
||||
|
||||
builder.Append(currentChar);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
46
Unosquare.RaspberryIO/Computer/OsInfo.cs
Normal file
46
Unosquare.RaspberryIO/Computer/OsInfo.cs
Normal file
@ -0,0 +1,46 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the OS Information
|
||||
/// </summary>
|
||||
public class OsInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// System name
|
||||
/// </summary>
|
||||
public string SysName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Node name
|
||||
/// </summary>
|
||||
public string NodeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Release level
|
||||
/// </summary>
|
||||
public string Release { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version level
|
||||
/// </summary>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hardware level
|
||||
/// </summary>
|
||||
public string Machine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Domain name
|
||||
/// </summary>
|
||||
public string DomainName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString() => $"{SysName} {Release} {Version}";
|
||||
}
|
||||
}
|
134
Unosquare.RaspberryIO/Computer/PiVersion.cs
Normal file
134
Unosquare.RaspberryIO/Computer/PiVersion.cs
Normal file
@ -0,0 +1,134 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the board revision codes of the different versions of the Raspberry Pi
|
||||
/// http://www.raspberrypi-spy.co.uk/2012/09/checking-your-raspberry-pi-board-version/
|
||||
/// </summary>
|
||||
public enum PiVersion
|
||||
{
|
||||
/// <summary>
|
||||
/// The unknown version
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev1
|
||||
/// </summary>
|
||||
ModelBRev1 = 0x0002,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev1 ec N0001
|
||||
/// </summary>
|
||||
ModelBRev1ECN0001 = 0x0003,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x04
|
||||
/// </summary>
|
||||
ModelBRev2x04 = 0x0004,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x05
|
||||
/// </summary>
|
||||
ModelBRev2x05 = 0x0005,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x06
|
||||
/// </summary>
|
||||
ModelBRev2x06 = 0x0006,
|
||||
|
||||
/// <summary>
|
||||
/// The model ax07
|
||||
/// </summary>
|
||||
ModelAx07 = 0x0007,
|
||||
|
||||
/// <summary>
|
||||
/// The model ax08
|
||||
/// </summary>
|
||||
ModelAx08 = 0x0008,
|
||||
|
||||
/// <summary>
|
||||
/// The model ax09
|
||||
/// </summary>
|
||||
ModelAx09 = 0x0009,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x0d
|
||||
/// </summary>
|
||||
ModelBRev2x0d,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x0e
|
||||
/// </summary>
|
||||
ModelBRev2x0e,
|
||||
|
||||
/// <summary>
|
||||
/// The model b rev2x0f
|
||||
/// </summary>
|
||||
ModelBRev2x0f = 0x000f,
|
||||
|
||||
/// <summary>
|
||||
/// The model b plus0x10
|
||||
/// </summary>
|
||||
ModelBPlus0x10 = 0x0010,
|
||||
|
||||
/// <summary>
|
||||
/// The model b plus0x13
|
||||
/// </summary>
|
||||
ModelBPlus0x13 = 0x0013,
|
||||
|
||||
/// <summary>
|
||||
/// The compute module0x11
|
||||
/// </summary>
|
||||
ComputeModule0x11 = 0x0011,
|
||||
|
||||
/// <summary>
|
||||
/// The compute module0x14
|
||||
/// </summary>
|
||||
ComputeModule0x14 = 0x0014,
|
||||
|
||||
/// <summary>
|
||||
/// The model a plus0x12
|
||||
/// </summary>
|
||||
ModelAPlus0x12 = 0x0012,
|
||||
|
||||
/// <summary>
|
||||
/// The model a plus0x15
|
||||
/// </summary>
|
||||
ModelAPlus0x15 = 0x0015,
|
||||
|
||||
/// <summary>
|
||||
/// The pi2 model B1V1 sony
|
||||
/// </summary>
|
||||
Pi2ModelB1v1Sony = 0xa01041,
|
||||
|
||||
/// <summary>
|
||||
/// The pi2 model B1V1 embest
|
||||
/// </summary>
|
||||
Pi2ModelB1v1Embest = 0xa21041,
|
||||
|
||||
/// <summary>
|
||||
/// The pi2 model B1V2
|
||||
/// </summary>
|
||||
Pi2ModelB1v2 = 0xa22042,
|
||||
|
||||
/// <summary>
|
||||
/// The pi zero1v2
|
||||
/// </summary>
|
||||
PiZero1v2 = 0x900092,
|
||||
|
||||
/// <summary>
|
||||
/// The pi zero1v3
|
||||
/// </summary>
|
||||
PiZero1v3 = 0x900093,
|
||||
|
||||
/// <summary>
|
||||
/// The pi3 model b sony
|
||||
/// </summary>
|
||||
Pi3ModelBSony = 0xa02082,
|
||||
|
||||
/// <summary>
|
||||
/// The pi3 model b embest
|
||||
/// </summary>
|
||||
Pi3ModelBEmbest = 0xa22082
|
||||
}
|
||||
}
|
344
Unosquare.RaspberryIO/Computer/SystemInfo.cs
Normal file
344
Unosquare.RaspberryIO/Computer/SystemInfo.cs
Normal file
@ -0,0 +1,344 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
using Native;
|
||||
using Swan.Abstractions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// http://raspberry-pi-guide.readthedocs.io/en/latest/system.html
|
||||
/// </summary>
|
||||
public sealed class SystemInfo : SingletonBase<SystemInfo>
|
||||
{
|
||||
private const string CpuInfoFilePath = "/proc/cpuinfo";
|
||||
private const string MemInfoFilePath = "/proc/meminfo";
|
||||
private const string UptimeFilePath = "/proc/uptime";
|
||||
private static readonly StringComparer StringComparer = StringComparer.InvariantCultureIgnoreCase;
|
||||
|
||||
private static readonly object SyncRoot = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="SystemInfo"/> class from being created.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception>
|
||||
private SystemInfo()
|
||||
{
|
||||
#region Obtain and format a property dictionary
|
||||
|
||||
var properties =
|
||||
typeof(SystemInfo).GetTypeInfo()
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
.Where(
|
||||
p =>
|
||||
p.CanWrite && p.CanRead &&
|
||||
(p.PropertyType == typeof(string) || p.PropertyType == typeof(string[])))
|
||||
.ToArray();
|
||||
var propDictionary = new Dictionary<string, PropertyInfo>(StringComparer);
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
propDictionary[prop.Name.Replace(" ", string.Empty).ToLowerInvariant().Trim()] = prop;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extract CPU information
|
||||
|
||||
if (File.Exists(CpuInfoFilePath))
|
||||
{
|
||||
var cpuInfoLines = File.ReadAllLines(CpuInfoFilePath);
|
||||
|
||||
foreach (var line in cpuInfoLines)
|
||||
{
|
||||
var lineParts = line.Split(new[] { ':' }, 2);
|
||||
if (lineParts.Length != 2)
|
||||
continue;
|
||||
|
||||
var propertyKey = lineParts[0].Trim().Replace(" ", string.Empty);
|
||||
var propertyStringValue = lineParts[1].Trim();
|
||||
|
||||
if (!propDictionary.ContainsKey(propertyKey)) continue;
|
||||
|
||||
var property = propDictionary[propertyKey];
|
||||
if (property.PropertyType == typeof(string))
|
||||
{
|
||||
property.SetValue(this, propertyStringValue);
|
||||
}
|
||||
else if (property.PropertyType == typeof(string[]))
|
||||
{
|
||||
var propertyArrayAvalue = propertyStringValue.Split(' ');
|
||||
property.SetValue(this, propertyArrayAvalue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extract Memory Information
|
||||
|
||||
if (File.Exists(MemInfoFilePath))
|
||||
{
|
||||
var memInfoLines = File.ReadAllLines(MemInfoFilePath);
|
||||
foreach (var line in memInfoLines)
|
||||
{
|
||||
var lineParts = line.Split(new[] { ':' }, 2);
|
||||
if (lineParts.Length != 2)
|
||||
continue;
|
||||
|
||||
if (lineParts[0].ToLowerInvariant().Trim().Equals("memtotal") == false)
|
||||
continue;
|
||||
|
||||
var memKb = lineParts[1].ToLowerInvariant().Trim().Replace("kb", string.Empty).Trim();
|
||||
|
||||
if (int.TryParse(memKb, out var parsedMem))
|
||||
{
|
||||
InstalledRam = parsedMem * 1024;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Board Version and Form Factor
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Revision) == false &&
|
||||
int.TryParse(
|
||||
Revision.ToUpperInvariant(),
|
||||
NumberStyles.HexNumber,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var boardVersion))
|
||||
{
|
||||
RaspberryPiVersion = PiVersion.Unknown;
|
||||
if (Enum.GetValues(typeof(PiVersion)).Cast<int>().Contains(boardVersion))
|
||||
{
|
||||
RaspberryPiVersion = (PiVersion)boardVersion;
|
||||
}
|
||||
}
|
||||
|
||||
WiringPiBoardRevision = WiringPi.PiBoardRev();
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* Ignore */
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Version Information
|
||||
|
||||
{
|
||||
var libParts = WiringPi.WiringPiLibrary.Split('.');
|
||||
var major = int.Parse(libParts[libParts.Length - 2]);
|
||||
var minor = int.Parse(libParts[libParts.Length - 1]);
|
||||
var version = new Version(major, minor);
|
||||
WiringPiVersion = version;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extract OS Info
|
||||
|
||||
try
|
||||
{
|
||||
Standard.Uname(out var unameInfo);
|
||||
OperatingSystem = new OsInfo
|
||||
{
|
||||
DomainName = unameInfo.DomainName,
|
||||
Machine = unameInfo.Machine,
|
||||
NodeName = unameInfo.NodeName,
|
||||
Release = unameInfo.Release,
|
||||
SysName = unameInfo.SysName,
|
||||
Version = unameInfo.Version
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
OperatingSystem = new OsInfo();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the wiring pi library version.
|
||||
/// </summary>
|
||||
public Version WiringPiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OS information.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The os information.
|
||||
/// </value>
|
||||
public OsInfo OperatingSystem { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Raspberry Pi version.
|
||||
/// </summary>
|
||||
public PiVersion RaspberryPiVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Wiring Pi board revision (1 or 2).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The wiring pi board revision.
|
||||
/// </value>
|
||||
public int WiringPiBoardRevision { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of processor cores.
|
||||
/// </summary>
|
||||
public int ProcessorCount
|
||||
{
|
||||
get
|
||||
{
|
||||
if (int.TryParse(Processor, out var outIndex))
|
||||
{
|
||||
return outIndex + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the installed ram in bytes.
|
||||
/// </summary>
|
||||
public int InstalledRam { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this CPU is little endian.
|
||||
/// </summary>
|
||||
public bool IsLittleEndian => BitConverter.IsLittleEndian;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU model name.
|
||||
/// </summary>
|
||||
public string ModelName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of supported CPU features.
|
||||
/// </summary>
|
||||
public string[] Features { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU implementer hex code.
|
||||
/// </summary>
|
||||
public string CpuImplementer { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU architecture code.
|
||||
/// </summary>
|
||||
public string CpuArchitecture { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU variant code.
|
||||
/// </summary>
|
||||
public string CpuVariant { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU part code.
|
||||
/// </summary>
|
||||
public string CpuPart { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CPU revision code.
|
||||
/// </summary>
|
||||
public string CpuRevision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hardware model number.
|
||||
/// </summary>
|
||||
public string Hardware { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hardware revision number.
|
||||
/// </summary>
|
||||
public string Revision { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the serial number.
|
||||
/// </summary>
|
||||
public string Serial { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system uptime (in seconds).
|
||||
/// </summary>
|
||||
public double Uptime
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(UptimeFilePath) == false) return 0;
|
||||
var parts = File.ReadAllText(UptimeFilePath).Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length >= 1 && float.TryParse(parts[0], out var result))
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* Ignore */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the uptime in TimeSpan.
|
||||
/// </summary>
|
||||
public TimeSpan UptimeTimeSpan => TimeSpan.FromSeconds(Uptime);
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder for processor index
|
||||
/// </summary>
|
||||
private string Processor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="string" /> that represents this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string" /> that represents this instance.
|
||||
/// </returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var properties = typeof(SystemInfo).GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.CanRead && (
|
||||
p.PropertyType == typeof(string) ||
|
||||
p.PropertyType == typeof(string[]) ||
|
||||
p.PropertyType == typeof(int) ||
|
||||
p.PropertyType == typeof(bool) ||
|
||||
p.PropertyType == typeof(TimeSpan)))
|
||||
.ToArray();
|
||||
|
||||
var properyValues = new List<string>
|
||||
{
|
||||
"System Information",
|
||||
$"\t{nameof(WiringPiVersion),-22}: {WiringPiVersion}",
|
||||
$"\t{nameof(RaspberryPiVersion),-22}: {RaspberryPiVersion}"
|
||||
};
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
if (property.PropertyType != typeof(string[]))
|
||||
{
|
||||
properyValues.Add($"\t{property.Name,-22}: {property.GetValue(this)}");
|
||||
}
|
||||
else if (property.GetValue(this) is string[] allValues)
|
||||
{
|
||||
var concatValues = string.Join(" ", allValues);
|
||||
properyValues.Add($"\t{property.Name,-22}: {concatValues}");
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Environment.NewLine, properyValues.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
23
Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs
Normal file
23
Unosquare.RaspberryIO/Computer/WirelessNetworkInfo.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Unosquare.RaspberryIO.Computer
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a wireless network information
|
||||
/// </summary>
|
||||
public class WirelessNetworkInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ESSID of the Wireless network.
|
||||
/// </summary>
|
||||
public string Name { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network quality.
|
||||
/// </summary>
|
||||
public string Quality { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is encrypted.
|
||||
/// </summary>
|
||||
public bool IsEncrypted { get; internal set; }
|
||||
}
|
||||
}
|
579
Unosquare.RaspberryIO/Gpio/Enums.cs
Normal file
579
Unosquare.RaspberryIO/Gpio/Enums.cs
Normal file
@ -0,0 +1,579 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the different drive modes of a GPIO pin
|
||||
/// </summary>
|
||||
public enum GpioPinDriveMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Input drive mode (perform reads)
|
||||
/// </summary>
|
||||
Input = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Output drive mode (perform writes)
|
||||
/// </summary>
|
||||
Output = 1,
|
||||
|
||||
/// <summary>
|
||||
/// PWM output mode (only certain pins support this -- 2 of them at the moment)
|
||||
/// </summary>
|
||||
PwmOutput = 2,
|
||||
|
||||
/// <summary>
|
||||
/// GPIO Clock output mode (only a pin supports this at this time)
|
||||
/// </summary>
|
||||
GpioClock = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GPIO pin resistor mode. This is used on input pins so that their
|
||||
/// lines are not floating
|
||||
/// </summary>
|
||||
public enum GpioPinResistorPullMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Pull resistor not active. Line floating
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Pull resistor sets a default value of 0 on no-connects
|
||||
/// </summary>
|
||||
PullDown = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Pull resistor sets a default value of 1 on no-connects
|
||||
/// </summary>
|
||||
PullUp = 2,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PWM mode.
|
||||
/// </summary>
|
||||
public enum PwmMode
|
||||
{
|
||||
/// <summary>
|
||||
/// PWM pulses are sent using mark-sign patterns (old school)
|
||||
/// </summary>
|
||||
MarkSign = 0,
|
||||
|
||||
/// <summary>
|
||||
/// PWM pulses are sent as a balanced signal (default, newer mode)
|
||||
/// </summary>
|
||||
Balanced = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different edge detection modes for pin interrupts
|
||||
/// </summary>
|
||||
public enum EdgeDetection
|
||||
{
|
||||
/// <summary>
|
||||
/// Assumes edge detection was already setup externally
|
||||
/// </summary>
|
||||
ExternalSetup = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Falling Edge
|
||||
/// </summary>
|
||||
FallingEdge = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Rising edge
|
||||
/// </summary>
|
||||
RisingEdge = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Both, rising and falling edges
|
||||
/// </summary>
|
||||
RisingAndFallingEdges = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the GPIO Pin values 0 for low, 1 for High
|
||||
/// </summary>
|
||||
public enum GpioPinValue
|
||||
{
|
||||
/// <summary>
|
||||
/// Digital high
|
||||
/// </summary>
|
||||
High = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Digital low
|
||||
/// </summary>
|
||||
Low = 0
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the Header connectors available
|
||||
/// </summary>
|
||||
public enum GpioHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Not defined
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// The P1 connector (main connector)
|
||||
/// </summary>
|
||||
P1,
|
||||
|
||||
/// <summary>
|
||||
/// The P5 connector (auxiliary, not commonly used)
|
||||
/// </summary>
|
||||
P5,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines all the available Wiring Pi Pin Numbers
|
||||
/// </summary>
|
||||
public enum WiringPiPin
|
||||
{
|
||||
/// <summary>
|
||||
/// The unknown
|
||||
/// </summary>
|
||||
Unknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// The pin00
|
||||
/// </summary>
|
||||
Pin00 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The pin01
|
||||
/// </summary>
|
||||
Pin01 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The pin02
|
||||
/// </summary>
|
||||
Pin02 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The pin03
|
||||
/// </summary>
|
||||
Pin03 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The pin04
|
||||
/// </summary>
|
||||
Pin04 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The pin05
|
||||
/// </summary>
|
||||
Pin05 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// The pin06
|
||||
/// </summary>
|
||||
Pin06 = 6,
|
||||
|
||||
/// <summary>
|
||||
/// The pin07
|
||||
/// </summary>
|
||||
Pin07 = 7,
|
||||
|
||||
/// <summary>
|
||||
/// The pin08
|
||||
/// </summary>
|
||||
Pin08 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// The pin09
|
||||
/// </summary>
|
||||
Pin09 = 9,
|
||||
|
||||
/// <summary>
|
||||
/// The pin10
|
||||
/// </summary>
|
||||
Pin10 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// The pin11
|
||||
/// </summary>
|
||||
Pin11 = 11,
|
||||
|
||||
/// <summary>
|
||||
/// The pin12
|
||||
/// </summary>
|
||||
Pin12 = 12,
|
||||
|
||||
/// <summary>
|
||||
/// The pin13
|
||||
/// </summary>
|
||||
Pin13 = 13,
|
||||
|
||||
/// <summary>
|
||||
/// The pin14
|
||||
/// </summary>
|
||||
Pin14 = 14,
|
||||
|
||||
/// <summary>
|
||||
/// The pin15
|
||||
/// </summary>
|
||||
Pin15 = 15,
|
||||
|
||||
/// <summary>
|
||||
/// The pin16
|
||||
/// </summary>
|
||||
Pin16 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// The pin17
|
||||
/// </summary>
|
||||
Pin17 = 17,
|
||||
|
||||
/// <summary>
|
||||
/// The pin18
|
||||
/// </summary>
|
||||
Pin18 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// The pin19
|
||||
/// </summary>
|
||||
Pin19 = 19,
|
||||
|
||||
/// <summary>
|
||||
/// The pin20
|
||||
/// </summary>
|
||||
Pin20 = 20,
|
||||
|
||||
/// <summary>
|
||||
/// The pin21
|
||||
/// </summary>
|
||||
Pin21 = 21,
|
||||
|
||||
/// <summary>
|
||||
/// The pin22
|
||||
/// </summary>
|
||||
Pin22 = 22,
|
||||
|
||||
/// <summary>
|
||||
/// The pin23
|
||||
/// </summary>
|
||||
Pin23 = 23,
|
||||
|
||||
/// <summary>
|
||||
/// The pin24
|
||||
/// </summary>
|
||||
Pin24 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// The pin25
|
||||
/// </summary>
|
||||
Pin25 = 25,
|
||||
|
||||
/// <summary>
|
||||
/// The pin26
|
||||
/// </summary>
|
||||
Pin26 = 26,
|
||||
|
||||
/// <summary>
|
||||
/// The pin27
|
||||
/// </summary>
|
||||
Pin27 = 27,
|
||||
|
||||
/// <summary>
|
||||
/// The pin28
|
||||
/// </summary>
|
||||
Pin28 = 28,
|
||||
|
||||
/// <summary>
|
||||
/// The pin29
|
||||
/// </summary>
|
||||
Pin29 = 29,
|
||||
|
||||
/// <summary>
|
||||
/// The pin30
|
||||
/// </summary>
|
||||
Pin30 = 30,
|
||||
|
||||
/// <summary>
|
||||
/// The pin31
|
||||
/// </summary>
|
||||
Pin31 = 31,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different pins on the P1 Header
|
||||
/// as commonly referenced by Raspberry Pi Documentation.
|
||||
/// Enumeration values correspond to the physical pin number.
|
||||
/// </summary>
|
||||
public enum P1
|
||||
{
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 02
|
||||
/// </summary>
|
||||
Gpio02 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 03
|
||||
/// </summary>
|
||||
Gpio03 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 04
|
||||
/// </summary>
|
||||
Gpio04 = 7,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 17
|
||||
/// </summary>
|
||||
Gpio17 = 11,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 27
|
||||
/// </summary>
|
||||
Gpio27 = 13,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 22
|
||||
/// </summary>
|
||||
Gpio22 = 15,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 10
|
||||
/// </summary>
|
||||
Gpio10 = 19,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 09
|
||||
/// </summary>
|
||||
Gpio09 = 21,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 11
|
||||
/// </summary>
|
||||
Gpio11 = 23,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 05
|
||||
/// </summary>
|
||||
Gpio05 = 29,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 06
|
||||
/// </summary>
|
||||
Gpio06 = 31,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 13
|
||||
/// </summary>
|
||||
Gpio13 = 33,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 19
|
||||
/// </summary>
|
||||
Gpio19 = 35,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 26
|
||||
/// </summary>
|
||||
Gpio26 = 37,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 14
|
||||
/// </summary>
|
||||
Gpio14 = 8,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 15
|
||||
/// </summary>
|
||||
Gpio15 = 10,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 18
|
||||
/// </summary>
|
||||
Gpio18 = 12,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 23
|
||||
/// </summary>
|
||||
Gpio23 = 16,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 24
|
||||
/// </summary>
|
||||
Gpio24 = 18,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 25
|
||||
/// </summary>
|
||||
Gpio25 = 22,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 08
|
||||
/// </summary>
|
||||
Gpio08 = 24,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 07
|
||||
/// </summary>
|
||||
Gpio07 = 26,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 12
|
||||
/// </summary>
|
||||
Gpio12 = 32,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 16
|
||||
/// </summary>
|
||||
Gpio16 = 36,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 20
|
||||
/// </summary>
|
||||
Gpio20 = 38,
|
||||
|
||||
/// <summary>
|
||||
/// Header P1, GPIO Pin 21
|
||||
/// </summary>
|
||||
Gpio21 = 40
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different pins on the P5 Header
|
||||
/// as commonly referenced by Raspberry Pi documentation.
|
||||
/// Enumeration values correspond to the physical pin number.
|
||||
/// </summary>
|
||||
public enum P5
|
||||
{
|
||||
/// <summary>
|
||||
/// Header P5, GPIO Pin 28
|
||||
/// </summary>
|
||||
Gpio28 = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Header P5, GPIO Pin 29
|
||||
/// </summary>
|
||||
Gpio29 = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Header P5, GPIO Pin 30
|
||||
/// </summary>
|
||||
Gpio30 = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Header P5, GPIO Pin 31
|
||||
/// </summary>
|
||||
Gpio31 = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different pin capabilities
|
||||
/// </summary>
|
||||
public enum PinCapability
|
||||
{
|
||||
/// <summary>
|
||||
/// General Purpose capability: Digital and Analog Read/Write
|
||||
/// </summary>
|
||||
GP,
|
||||
|
||||
/// <summary>
|
||||
/// General Purpose Clock (not PWM)
|
||||
/// </summary>
|
||||
GPCLK,
|
||||
|
||||
/// <summary>
|
||||
/// i2c data channel
|
||||
/// </summary>
|
||||
I2CSDA,
|
||||
|
||||
/// <summary>
|
||||
/// i2c clock channel
|
||||
/// </summary>
|
||||
I2CSCL,
|
||||
|
||||
/// <summary>
|
||||
/// SPI Master Out, Slave In channel
|
||||
/// </summary>
|
||||
SPIMOSI,
|
||||
|
||||
/// <summary>
|
||||
/// SPI Master In, Slave Out channel
|
||||
/// </summary>
|
||||
SPIMISO,
|
||||
|
||||
/// <summary>
|
||||
/// SPI Clock channel
|
||||
/// </summary>
|
||||
SPICLK,
|
||||
|
||||
/// <summary>
|
||||
/// SPI Chip Select Channel
|
||||
/// </summary>
|
||||
SPICS,
|
||||
|
||||
/// <summary>
|
||||
/// UART Request to Send Channel
|
||||
/// </summary>
|
||||
UARTRTS,
|
||||
|
||||
/// <summary>
|
||||
/// UART Transmit Channel
|
||||
/// </summary>
|
||||
UARTTXD,
|
||||
|
||||
/// <summary>
|
||||
/// UART Receive Channel
|
||||
/// </summary>
|
||||
UARTRXD,
|
||||
|
||||
/// <summary>
|
||||
/// Hardware Pule Width Modulation
|
||||
/// </summary>
|
||||
PWM
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the SPI channel numbers
|
||||
/// </summary>
|
||||
internal enum SpiChannelNumber
|
||||
{
|
||||
/// <summary>
|
||||
/// The channel 0
|
||||
/// </summary>
|
||||
Channel0 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The channel 1
|
||||
/// </summary>
|
||||
Channel1 = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines GPIO controller initialization modes
|
||||
/// </summary>
|
||||
internal enum ControllerMode
|
||||
{
|
||||
/// <summary>
|
||||
/// The not initialized
|
||||
/// </summary>
|
||||
NotInitialized,
|
||||
|
||||
/// <summary>
|
||||
/// The direct with wiring pi pins
|
||||
/// </summary>
|
||||
DirectWithWiringPiPins,
|
||||
|
||||
/// <summary>
|
||||
/// The direct with BCM pins
|
||||
/// </summary>
|
||||
DirectWithBcmPins,
|
||||
|
||||
/// <summary>
|
||||
/// The direct with header pins
|
||||
/// </summary>
|
||||
DirectWithHeaderPins,
|
||||
|
||||
/// <summary>
|
||||
/// The file stream with hardware pins
|
||||
/// </summary>
|
||||
FileStreamWithHardwarePins,
|
||||
}
|
||||
}
|
594
Unosquare.RaspberryIO/Gpio/GpioController.cs
Normal file
594
Unosquare.RaspberryIO/Gpio/GpioController.cs
Normal file
@ -0,0 +1,594 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using Native;
|
||||
using Swan;
|
||||
using Swan.Abstractions;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a singleton of the Raspberry Pi GPIO controller
|
||||
/// as an IReadOnlyCollection of GpioPins
|
||||
/// Low level operations are accomplished by using the Wiring Pi library.
|
||||
/// Use the Instance property to access the singleton's instance
|
||||
/// </summary>
|
||||
public sealed class GpioController : SingletonBase<GpioController>, IReadOnlyCollection<GpioPin>
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private const string WiringPiCodesEnvironmentVariable = "WIRINGPI_CODES";
|
||||
private static readonly object SyncRoot = new object();
|
||||
private readonly ReadOnlyCollection<GpioPin> _pinCollection;
|
||||
private readonly ReadOnlyDictionary<int, GpioPin> _headerP1Pins;
|
||||
private readonly ReadOnlyDictionary<int, GpioPin> _headerP5Pins;
|
||||
private readonly Dictionary<WiringPiPin, GpioPin> _pinsByWiringPiPinNumber = new Dictionary<WiringPiPin, GpioPin>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors and Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="GpioController" /> class from being created.
|
||||
/// It in turn initializes the controller and registers the pin -- in that order.
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">Unable to initialize the GPIO controller.</exception>
|
||||
private GpioController()
|
||||
{
|
||||
if (_pinCollection != null)
|
||||
return;
|
||||
|
||||
if (IsInitialized == false)
|
||||
{
|
||||
var initResult = Initialize(ControllerMode.DirectWithWiringPiPins);
|
||||
if (initResult == false)
|
||||
throw new Exception("Unable to initialize the GPIO controller.");
|
||||
}
|
||||
|
||||
#region Pin Registration (32 WiringPi Pins)
|
||||
|
||||
RegisterPin(GpioPin.Pin00.Value);
|
||||
RegisterPin(GpioPin.Pin01.Value);
|
||||
RegisterPin(GpioPin.Pin02.Value);
|
||||
RegisterPin(GpioPin.Pin03.Value);
|
||||
RegisterPin(GpioPin.Pin04.Value);
|
||||
RegisterPin(GpioPin.Pin05.Value);
|
||||
RegisterPin(GpioPin.Pin06.Value);
|
||||
RegisterPin(GpioPin.Pin07.Value);
|
||||
RegisterPin(GpioPin.Pin08.Value);
|
||||
RegisterPin(GpioPin.Pin09.Value);
|
||||
RegisterPin(GpioPin.Pin10.Value);
|
||||
RegisterPin(GpioPin.Pin11.Value);
|
||||
RegisterPin(GpioPin.Pin12.Value);
|
||||
RegisterPin(GpioPin.Pin13.Value);
|
||||
RegisterPin(GpioPin.Pin14.Value);
|
||||
RegisterPin(GpioPin.Pin15.Value);
|
||||
RegisterPin(GpioPin.Pin16.Value);
|
||||
RegisterPin(GpioPin.Pin17.Value);
|
||||
RegisterPin(GpioPin.Pin18.Value);
|
||||
RegisterPin(GpioPin.Pin19.Value);
|
||||
RegisterPin(GpioPin.Pin20.Value);
|
||||
RegisterPin(GpioPin.Pin21.Value);
|
||||
RegisterPin(GpioPin.Pin22.Value);
|
||||
RegisterPin(GpioPin.Pin23.Value);
|
||||
RegisterPin(GpioPin.Pin24.Value);
|
||||
RegisterPin(GpioPin.Pin25.Value);
|
||||
RegisterPin(GpioPin.Pin26.Value);
|
||||
RegisterPin(GpioPin.Pin27.Value);
|
||||
RegisterPin(GpioPin.Pin28.Value);
|
||||
RegisterPin(GpioPin.Pin29.Value);
|
||||
RegisterPin(GpioPin.Pin30.Value);
|
||||
RegisterPin(GpioPin.Pin31.Value);
|
||||
|
||||
#endregion
|
||||
|
||||
_pinCollection = new ReadOnlyCollection<GpioPin>(_pinsByWiringPiPinNumber.Values.ToArray());
|
||||
var headerP1 = new Dictionary<int, GpioPin>(_pinCollection.Count);
|
||||
var headerP5 = new Dictionary<int, GpioPin>(_pinCollection.Count);
|
||||
foreach (var pin in _pinCollection)
|
||||
{
|
||||
var target = pin.Header == GpioHeader.P1 ? headerP1 : headerP5;
|
||||
target[pin.HeaderPinNumber] = pin;
|
||||
}
|
||||
|
||||
_headerP1Pins = new ReadOnlyDictionary<int, GpioPin>(headerP1);
|
||||
_headerP5Pins = new ReadOnlyDictionary<int, GpioPin>(headerP5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the underlying GPIO controller has been initialized properly.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the controller is properly initialized; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public static bool IsInitialized
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return Mode != ControllerMode.NotInitialized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of registered pins in the controller.
|
||||
/// </summary>
|
||||
public int Count => _pinCollection.Count;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pin Addressing
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PWM base frequency (in Hz).
|
||||
/// </summary>
|
||||
public int PwmBaseFrequency => 19200000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a red-only collection of all registered pins.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<GpioPin> Pins => _pinCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Provides all the pins on Header P1 of the Pi as a lookup by physical header pin number.
|
||||
/// This header is the main header and it is the one commonly used.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<int, GpioPin> HeaderP1 => _headerP1Pins;
|
||||
|
||||
/// <summary>
|
||||
/// Provides all the pins on Header P5 of the Pi as a lookup by physical header pin number.
|
||||
/// This header is the secondary header and it is rarely used.
|
||||
/// </summary>
|
||||
public ReadOnlyDictionary<int, GpioPin> HeaderP5 => _headerP5Pins;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Individual Pin Properties
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 00.
|
||||
/// </summary>
|
||||
public GpioPin Pin00 => GpioPin.Pin00.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 01.
|
||||
/// </summary>
|
||||
public GpioPin Pin01 => GpioPin.Pin01.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 02.
|
||||
/// </summary>
|
||||
public GpioPin Pin02 => GpioPin.Pin02.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 03.
|
||||
/// </summary>
|
||||
public GpioPin Pin03 => GpioPin.Pin03.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 04.
|
||||
/// </summary>
|
||||
public GpioPin Pin04 => GpioPin.Pin04.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 05.
|
||||
/// </summary>
|
||||
public GpioPin Pin05 => GpioPin.Pin05.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 06.
|
||||
/// </summary>
|
||||
public GpioPin Pin06 => GpioPin.Pin06.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 07.
|
||||
/// </summary>
|
||||
public GpioPin Pin07 => GpioPin.Pin07.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 08.
|
||||
/// </summary>
|
||||
public GpioPin Pin08 => GpioPin.Pin08.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 09.
|
||||
/// </summary>
|
||||
public GpioPin Pin09 => GpioPin.Pin09.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 10.
|
||||
/// </summary>
|
||||
public GpioPin Pin10 => GpioPin.Pin10.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 11.
|
||||
/// </summary>
|
||||
public GpioPin Pin11 => GpioPin.Pin11.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 12.
|
||||
/// </summary>
|
||||
public GpioPin Pin12 => GpioPin.Pin12.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 13.
|
||||
/// </summary>
|
||||
public GpioPin Pin13 => GpioPin.Pin13.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 14.
|
||||
/// </summary>
|
||||
public GpioPin Pin14 => GpioPin.Pin14.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 15.
|
||||
/// </summary>
|
||||
public GpioPin Pin15 => GpioPin.Pin15.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 16.
|
||||
/// </summary>
|
||||
public GpioPin Pin16 => GpioPin.Pin16.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 17.
|
||||
/// </summary>
|
||||
public GpioPin Pin17 => GpioPin.Pin17.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 18.
|
||||
/// </summary>
|
||||
public GpioPin Pin18 => GpioPin.Pin18.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 19.
|
||||
/// </summary>
|
||||
public GpioPin Pin19 => GpioPin.Pin19.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 20.
|
||||
/// </summary>
|
||||
public GpioPin Pin20 => GpioPin.Pin20.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 21.
|
||||
/// </summary>
|
||||
public GpioPin Pin21 => GpioPin.Pin21.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 22.
|
||||
/// </summary>
|
||||
public GpioPin Pin22 => GpioPin.Pin22.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 23.
|
||||
/// </summary>
|
||||
public GpioPin Pin23 => GpioPin.Pin23.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 24.
|
||||
/// </summary>
|
||||
public GpioPin Pin24 => GpioPin.Pin24.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 25.
|
||||
/// </summary>
|
||||
public GpioPin Pin25 => GpioPin.Pin25.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 26.
|
||||
/// </summary>
|
||||
public GpioPin Pin26 => GpioPin.Pin26.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 27.
|
||||
/// </summary>
|
||||
public GpioPin Pin27 => GpioPin.Pin27.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 28.
|
||||
/// </summary>
|
||||
public GpioPin Pin28 => GpioPin.Pin28.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 29.
|
||||
/// </summary>
|
||||
public GpioPin Pin29 => GpioPin.Pin29.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 30.
|
||||
/// </summary>
|
||||
public GpioPin Pin30 => GpioPin.Pin30.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Provides direct access to Pin known to Wiring Pi (not the pin header number) as Pin 31.
|
||||
/// </summary>
|
||||
public GpioPin Pin31 => GpioPin.Pin31.Value;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Indexers
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the initialization mode.
|
||||
/// </summary>
|
||||
private static ControllerMode Mode { get; set; } = ControllerMode.NotInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GpioPin"/> with the specified Wiring Pi pin number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="GpioPin"/>.
|
||||
/// </value>
|
||||
/// <param name="pinNumber">The pin number.</param>
|
||||
/// <returns>A reference to the GPIO pin</returns>
|
||||
public GpioPin this[WiringPiPin pinNumber] => _pinsByWiringPiPinNumber[pinNumber];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GpioPin"/> with the specified pin number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="GpioPin"/>.
|
||||
/// </value>
|
||||
/// <param name="pinNumber">The pin number.</param>
|
||||
/// <returns>A reference to the GPIO pin</returns>
|
||||
public GpioPin this[P1 pinNumber] => HeaderP1[(int)pinNumber];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GpioPin"/> with the specified pin number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="GpioPin"/>.
|
||||
/// </value>
|
||||
/// <param name="pinNumber">The pin number.</param>
|
||||
/// <returns>A reference to the GPIO pin</returns>
|
||||
public GpioPin this[P5 pinNumber] => HeaderP5[(int)pinNumber];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="GpioPin"/> with the specified Wiring Pi pin number.
|
||||
/// Use the HeaderP1 and HeaderP5 lookups if you would like to retrieve pins by physical pin number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="GpioPin"/>.
|
||||
/// </value>
|
||||
/// <param name="wiringPiPinNumber">The pin number as defined by Wiring Pi. This is not the header pin number as pin number in headers are obvoisly repeating.</param>
|
||||
/// <returns>A reference to the GPIO pin</returns>
|
||||
/// <exception cref="IndexOutOfRangeException">When the pin index is not found</exception>
|
||||
public GpioPin this[int wiringPiPinNumber]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Enum.IsDefined(typeof(WiringPiPin), wiringPiPinNumber) == false)
|
||||
throw new IndexOutOfRangeException($"Pin {wiringPiPinNumber} is not registered in the GPIO controller.");
|
||||
|
||||
return _pinsByWiringPiPinNumber[(WiringPiPin)wiringPiPinNumber];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pin Group Methods (Read, Write, Pad Drive)
|
||||
|
||||
/// <summary>
|
||||
/// This sets the “strength” of the pad drivers for a particular group of pins.
|
||||
/// There are 3 groups of pins and the drive strength is from 0 to 7.
|
||||
/// Do not use this unless you know what you are doing.
|
||||
/// </summary>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public void SetPadDrive(int group, int value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
WiringPi.SetPadDrive(group, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This sets the “strength” of the pad drivers for a particular group of pins.
|
||||
/// There are 3 groups of pins and the drive strength is from 0 to 7.
|
||||
/// Do not use this unless you know what you are doing.
|
||||
/// </summary>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task SetPadDriveAsync(int group, int value) => Task.Run(() => { SetPadDrive(group, value); });
|
||||
|
||||
/// <summary>
|
||||
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to set all 8 bits at once to a particular value,
|
||||
/// although it still takes two write operations to the Pi’s GPIO hardware.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <exception cref="InvalidOperationException">PinMode</exception>
|
||||
public void WriteByte(byte value)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (this.Skip(0).Take(8).Any(p => p.PinMode != GpioPinDriveMode.Output))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"All firts 8 pins (0 to 7) need their {nameof(GpioPin.PinMode)} to be set to {GpioPinDriveMode.Output}");
|
||||
}
|
||||
|
||||
WiringPi.DigitalWriteByte(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to set all 8 bits at once to a particular value,
|
||||
/// although it still takes two write operations to the Pi’s GPIO hardware.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteByteAsync(byte value) => Task.Run(() => { WriteByte(value); });
|
||||
|
||||
/// <summary>
|
||||
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to get all 8 bits at once to a particular value.
|
||||
/// Please note this function is undocumented and unsopported
|
||||
/// </summary>
|
||||
/// <returns>A byte from the GPIO</returns>
|
||||
/// <exception cref="InvalidOperationException">PinMode</exception>
|
||||
public byte ReadByte()
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (this.Skip(0).Take(8).Any(p =>
|
||||
p.PinMode != GpioPinDriveMode.Input && p.PinMode != GpioPinDriveMode.Output))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"All firts 8 pins (0 to 7) need their {nameof(GpioPin.PinMode)} to be set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}");
|
||||
}
|
||||
|
||||
return (byte)WiringPi.DigitalReadByte();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to get all 8 bits at once to a particular value.
|
||||
/// Please note this function is undocumented and unsopported
|
||||
/// </summary>
|
||||
/// <returns>A byte from the GPIO</returns>
|
||||
public Task<byte> ReadByteAsync() => Task.Run(() => ReadByte());
|
||||
|
||||
#endregion
|
||||
|
||||
#region IReadOnlyCollection Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
public IEnumerator<GpioPin> GetEnumerator() => _pinCollection.GetEnumerator();
|
||||
|
||||
/// <summary>
|
||||
/// Returns an enumerator that iterates through the collection.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
|
||||
/// </returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => _pinCollection.GetEnumerator();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper and Init Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the GPIO pin by BCM pin number.
|
||||
/// </summary>
|
||||
/// <param name="bcmPinNumber">The BCM pin number.</param>
|
||||
/// <returns>The GPIO pin</returns>
|
||||
public GpioPin GetGpioPinByBcmPinNumber(int bcmPinNumber) => this.First(pin => pin.BcmPinNumber == bcmPinNumber);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the Wirings Pi pin number to the BCM pin number.
|
||||
/// </summary>
|
||||
/// <param name="wiringPiPinNumber">The wiring pi pin number.</param>
|
||||
/// <returns>The converted pin</returns>
|
||||
internal static int WiringPiToBcmPinNumber(int wiringPiPinNumber)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return WiringPi.WpiPinToGpio(wiringPiPinNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the Physical (Header) pin number to BCM pin number.
|
||||
/// </summary>
|
||||
/// <param name="headerPinNumber">The header pin number.</param>
|
||||
/// <returns>The converted pin</returns>
|
||||
internal static int HaderToBcmPinNumber(int headerPinNumber)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return WiringPi.PhysPinToGpio(headerPinNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Short-hand method of registering pins
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
private void RegisterPin(GpioPin pin)
|
||||
{
|
||||
if (_pinsByWiringPiPinNumber.ContainsKey(pin.WiringPiPinNumber) == false)
|
||||
_pinsByWiringPiPinNumber[pin.WiringPiPinNumber] = pin;
|
||||
else
|
||||
throw new InvalidOperationException($"Pin {pin.WiringPiPinNumber} has been registered");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the controller given the initialization mode and pin numbering scheme
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <returns>True when successful.</returns>
|
||||
/// <exception cref="PlatformNotSupportedException">
|
||||
/// This library does not support the platform
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">Library was already Initialized</exception>
|
||||
/// <exception cref="ArgumentException">The init mode is invalid</exception>
|
||||
private bool Initialize(ControllerMode mode)
|
||||
{
|
||||
if (Runtime.OS != Swan.OperatingSystem.Unix)
|
||||
throw new PlatformNotSupportedException("This library does not support the platform");
|
||||
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (IsInitialized)
|
||||
throw new InvalidOperationException($"Cannot call {nameof(Initialize)} more than once.");
|
||||
|
||||
Environment.SetEnvironmentVariable(WiringPiCodesEnvironmentVariable, "1", EnvironmentVariableTarget.Process);
|
||||
int setpuResult;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case ControllerMode.DirectWithWiringPiPins:
|
||||
{
|
||||
setpuResult = WiringPi.WiringPiSetup();
|
||||
break;
|
||||
}
|
||||
|
||||
case ControllerMode.DirectWithBcmPins:
|
||||
{
|
||||
setpuResult = WiringPi.WiringPiSetupGpio();
|
||||
break;
|
||||
}
|
||||
|
||||
case ControllerMode.DirectWithHeaderPins:
|
||||
{
|
||||
setpuResult = WiringPi.WiringPiSetupPhys();
|
||||
break;
|
||||
}
|
||||
|
||||
case ControllerMode.FileStreamWithHardwarePins:
|
||||
{
|
||||
setpuResult = WiringPi.WiringPiSetupSys();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new ArgumentException($"'{mode}' is not a valid initialization mode.");
|
||||
}
|
||||
}
|
||||
|
||||
Mode = setpuResult == 0 ? mode : ControllerMode.NotInitialized;
|
||||
return IsInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
201
Unosquare.RaspberryIO/Gpio/GpioPin.Factory.cs
Normal file
201
Unosquare.RaspberryIO/Gpio/GpioPin.Factory.cs
Normal file
@ -0,0 +1,201 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using System;
|
||||
|
||||
public partial class GpioPin
|
||||
{
|
||||
#region Static Pin Definitions
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin08 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin08, 3)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
|
||||
Name = "BCM 2 (SDA)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin09 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin09, 5)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL },
|
||||
Name = "BCM 3 (SCL)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin07 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin07, 7)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.GPCLK },
|
||||
Name = "BCM 4 (GPCLK0)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin00 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin00, 11)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.UARTRTS },
|
||||
Name = "BCM 17"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin02 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin02, 13)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 27"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin03 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin03, 15)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 22"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin12 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin12, 19)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI },
|
||||
Name = "BCM 10 (MOSI)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin13 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin13, 21)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO },
|
||||
Name = "BCM 9 (MISO)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin14 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin14, 23)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK },
|
||||
Name = "BCM 11 (SCLCK)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin30 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin30, 27)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.I2CSDA },
|
||||
Name = "BCM 0 (ID_SD)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin31 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin31, 28)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.I2CSCL },
|
||||
Name = "BCM 1 (ID_SC)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin11 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin11, 26)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPICS },
|
||||
Name = "BCM 7 (CE1)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin10 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin10, 24)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPICS },
|
||||
Name = "BCM 8 (CE0)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin06 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin06, 22)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 25"
|
||||
});
|
||||
internal static readonly Lazy<GpioPin> Pin05 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin05, 18)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 24"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin04 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin04, 16)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 23"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin01 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin01, 12)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.PWM },
|
||||
Name = "BCM 18 (PWM0)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin16 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin16, 10)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.UARTRXD },
|
||||
Name = "BCM 15 (RXD)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin15 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin15, 8)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.UARTTXD },
|
||||
Name = "BCM 14 (TXD)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin21 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin21, 29)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 5"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin22 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin22, 31)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 6"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin23 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin23, 33)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.PWM },
|
||||
Name = "BCM 13 (PWM1)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin24 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin24, 35)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMISO },
|
||||
Name = "BCM 19 (MISO)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin25 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin25, 37)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 26"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin29 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin29, 40)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPICLK },
|
||||
Name = "BCM 21 (SCLK)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin28 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin28, 38)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.SPIMOSI },
|
||||
Name = "BCM 20 (MOSI)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin27 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin27, 36)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 16"
|
||||
});
|
||||
internal static readonly Lazy<GpioPin> Pin26 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin26, 32)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 12 (PWM0)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin17 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin17, 3)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSDA },
|
||||
Name = "BCM 28 (SDA)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin18 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin18, 4)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP, PinCapability.I2CSCL },
|
||||
Name = "BCM 29 (SCL)"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin19 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin19, 5)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 30"
|
||||
});
|
||||
|
||||
internal static readonly Lazy<GpioPin> Pin20 = new Lazy<GpioPin>(() => new GpioPin(WiringPiPin.Pin20, 6)
|
||||
{
|
||||
Capabilities = new[] { PinCapability.GP },
|
||||
Name = "BCM 31"
|
||||
});
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
648
Unosquare.RaspberryIO/Gpio/GpioPin.cs
Normal file
648
Unosquare.RaspberryIO/Gpio/GpioPin.cs
Normal file
@ -0,0 +1,648 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using Native;
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a GPIO Pin, its location and its capabilities.
|
||||
/// Full pin reference available here:
|
||||
/// http://pinout.xyz/pinout/pin31_gpio6 and http://wiringpi.com/pins/
|
||||
/// </summary>
|
||||
public sealed partial class GpioPin
|
||||
{
|
||||
#region Property Backing
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private GpioPinDriveMode m_PinMode;
|
||||
private GpioPinResistorPullMode m_ResistorPullMode;
|
||||
private int m_PwmRegister;
|
||||
private PwmMode m_PwmMode = PwmMode.Balanced;
|
||||
private uint m_PwmRange = 1024;
|
||||
private int m_PwmClockDivisor = 1;
|
||||
private int m_SoftPwmValue = -1;
|
||||
private int m_SoftToneFrequency = -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GpioPin"/> class.
|
||||
/// </summary>
|
||||
/// <param name="wiringPiPinNumber">The wiring pi pin number.</param>
|
||||
/// <param name="headerPinNumber">The header pin number.</param>
|
||||
private GpioPin(WiringPiPin wiringPiPinNumber, int headerPinNumber)
|
||||
{
|
||||
PinNumber = (int)wiringPiPinNumber;
|
||||
WiringPiPinNumber = wiringPiPinNumber;
|
||||
BcmPinNumber = GpioController.WiringPiToBcmPinNumber((int)wiringPiPinNumber);
|
||||
HeaderPinNumber = headerPinNumber;
|
||||
Header = (PinNumber >= 17 && PinNumber <= 20) ? GpioHeader.P5 : GpioHeader.P1;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pin Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Wiring Pi pin number as an integer.
|
||||
/// </summary>
|
||||
public int PinNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the WiringPi Pin number
|
||||
/// </summary>
|
||||
public WiringPiPin WiringPiPinNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BCM chip (hardware) pin number.
|
||||
/// </summary>
|
||||
public int BcmPinNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or the physical header (physical board) pin number.
|
||||
/// </summary>
|
||||
public int HeaderPinNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pin's header (physical board) location.
|
||||
/// </summary>
|
||||
public GpioHeader Header { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the friendly name of the pin.
|
||||
/// </summary>
|
||||
public string Name { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hardware mode capabilities of this pin.
|
||||
/// </summary>
|
||||
public PinCapability[] Capabilities { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hardware-Specific Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pin operating mode.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The pin mode.
|
||||
/// </value>
|
||||
/// <exception cref="NotSupportedException">Thrown when a pin does not support the given operation mode.</exception>
|
||||
public GpioPinDriveMode PinMode
|
||||
{
|
||||
get => m_PinMode;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var mode = value;
|
||||
if ((mode == GpioPinDriveMode.GpioClock && Capabilities.Contains(PinCapability.GPCLK) == false) ||
|
||||
(mode == GpioPinDriveMode.PwmOutput && Capabilities.Contains(PinCapability.PWM) == false) ||
|
||||
(mode == GpioPinDriveMode.Input && Capabilities.Contains(PinCapability.GP) == false) ||
|
||||
(mode == GpioPinDriveMode.Output && Capabilities.Contains(PinCapability.GP) == false))
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Pin {WiringPiPinNumber} '{Name}' does not support mode '{mode}'. Pin capabilities are limited to: {string.Join(", ", Capabilities)}");
|
||||
}
|
||||
|
||||
WiringPi.PinMode(PinNumber, (int)mode);
|
||||
m_PinMode = mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interrupt callback. Returns null if no interrupt
|
||||
/// has been registered.
|
||||
/// </summary>
|
||||
public InterruptServiceRoutineCallback InterruptCallback { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the interrupt edge detection mode.
|
||||
/// </summary>
|
||||
public EdgeDetection InterruptEdgeDetection { get; private set; } = EdgeDetection.ExternalSetup;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hardware PWM Members
|
||||
|
||||
/// <summary>
|
||||
/// This sets or gets the pull-up or pull-down resistor mode on the pin, which should be set as an input.
|
||||
/// Unlike the Arduino, the BCM2835 has both pull-up an down internal resistors.
|
||||
/// The parameter pud should be; PUD_OFF, (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v)
|
||||
/// The internal pull up/down resistors have a value of approximately 50KΩ on the Raspberry Pi.
|
||||
/// </summary>
|
||||
public GpioPinResistorPullMode InputPullMode
|
||||
{
|
||||
get => PinMode == GpioPinDriveMode.Input ? m_ResistorPullMode : GpioPinResistorPullMode.Off;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Input)
|
||||
{
|
||||
m_ResistorPullMode = GpioPinResistorPullMode.Off;
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to set the {nameof(InputPullMode)} for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Setting the {nameof(InputPullMode)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}");
|
||||
}
|
||||
|
||||
WiringPi.PullUpDnControl(PinNumber, (int)value);
|
||||
m_ResistorPullMode = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PWM register. Values should be between 0 and 1024
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The PWM register.
|
||||
/// </value>
|
||||
public int PwmRegister
|
||||
{
|
||||
get => m_PwmRegister;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.PwmOutput)
|
||||
{
|
||||
m_PwmRegister = 0;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to write PWM register for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Writing the PWM register is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}");
|
||||
}
|
||||
|
||||
var val = value.Clamp(0, 1024);
|
||||
|
||||
WiringPi.PwmWrite(PinNumber, val);
|
||||
m_PwmRegister = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The PWM generator can run in 2 modes – “balanced” and “mark:space”. The mark:space mode is traditional,
|
||||
/// however the default mode in the Pi is “balanced”.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The PWM mode.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">When pin mode is not set a Pwn output</exception>
|
||||
public PwmMode PwmMode
|
||||
{
|
||||
get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmMode : PwmMode.Balanced;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.PwmOutput)
|
||||
{
|
||||
m_PwmMode = PwmMode.Balanced;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to set PWM mode for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Setting the PWM mode is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}");
|
||||
}
|
||||
|
||||
WiringPi.PwmSetMode((int)value);
|
||||
m_PwmMode = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This sets the range register in the PWM generator. The default is 1024.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The PWM range.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">When pin mode is not set to PWM output</exception>
|
||||
public uint PwmRange
|
||||
{
|
||||
get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmRange : 0;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.PwmOutput)
|
||||
{
|
||||
m_PwmRange = 1024;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to set PWM range for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Setting the PWM range is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}");
|
||||
}
|
||||
|
||||
WiringPi.PwmSetRange(value);
|
||||
m_PwmRange = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the PWM clock divisor.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The PWM clock divisor.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">When pin mode is not set to PWM output</exception>
|
||||
public int PwmClockDivisor
|
||||
{
|
||||
get => PinMode == GpioPinDriveMode.PwmOutput ? m_PwmClockDivisor : 0;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.PwmOutput)
|
||||
{
|
||||
m_PwmClockDivisor = 1;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to set PWM range for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Setting the PWM range is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.PwmOutput}");
|
||||
}
|
||||
|
||||
WiringPi.PwmSetClock(value);
|
||||
m_PwmClockDivisor = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Software Tone Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is in software based tone generator mode.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is in soft tone mode; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsInSoftToneMode => m_SoftToneFrequency >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the soft tone frequency. 0 to 5000 Hz is typical
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soft tone frequency.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">When soft tones cannot be initialized on the pin</exception>
|
||||
public int SoftToneFrequency
|
||||
{
|
||||
get => m_SoftToneFrequency;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (IsInSoftToneMode == false)
|
||||
{
|
||||
var setupResult = WiringPi.SoftToneCreate(PinNumber);
|
||||
if (setupResult != 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to initialize soft tone on pin {PinNumber}. Error Code: {setupResult}");
|
||||
}
|
||||
}
|
||||
|
||||
WiringPi.SoftToneWrite(PinNumber, value);
|
||||
m_SoftToneFrequency = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Software PWM Members
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this pin is in software based PWM mode.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is in soft PWM mode; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsInSoftPwmMode => m_SoftPwmValue >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the software PWM value on the pin.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The soft PWM value.
|
||||
/// </value>
|
||||
/// <exception cref="InvalidOperationException">StartSoftPwm</exception>
|
||||
public int SoftPwmValue
|
||||
{
|
||||
get => m_SoftPwmValue;
|
||||
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (IsInSoftPwmMode && value >= 0)
|
||||
{
|
||||
WiringPi.SoftPwmWrite(PinNumber, value);
|
||||
m_SoftPwmValue = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Software PWM requires a call to {nameof(StartSoftPwm)}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the software PWM range used upon starting the PWM.
|
||||
/// </summary>
|
||||
public int SoftPwmRange { get; private set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// Starts the software based PWM on this pin.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <exception cref="NotSupportedException">When the pin does not suppoert PWM</exception>
|
||||
/// <exception cref="InvalidOperationException">StartSoftPwm
|
||||
/// or</exception>
|
||||
public void StartSoftPwm(int value, int range)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (Capabilities.Contains(PinCapability.GP) == false)
|
||||
throw new NotSupportedException($"Pin {PinNumber} does not support software PWM");
|
||||
|
||||
if (IsInSoftPwmMode)
|
||||
throw new InvalidOperationException($"{nameof(StartSoftPwm)} has already been called.");
|
||||
|
||||
var startResult = WiringPi.SoftPwmCreate(PinNumber, value, range);
|
||||
|
||||
if (startResult == 0)
|
||||
{
|
||||
m_SoftPwmValue = value;
|
||||
SoftPwmRange = range;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Could not start software based PWM on pin {PinNumber}. Error code: {startResult}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Output Mode (Write) Members
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified pin value.
|
||||
/// This method performs a digital write
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public void Write(GpioPinValue value)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Output)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to write to pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}");
|
||||
}
|
||||
|
||||
WiringPi.DigitalWrite(PinNumber, (int)value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the value asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteAsync(GpioPinValue value) => Task.Run(() => { Write(value); });
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified bit value.
|
||||
/// This method performs a digital write
|
||||
/// </summary>
|
||||
/// <param name="value">if set to <c>true</c> [value].</param>
|
||||
public void Write(bool value)
|
||||
=> Write(value ? GpioPinValue.High : GpioPinValue.Low);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified bit value.
|
||||
/// This method performs a digital write
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>
|
||||
/// The awaitable task
|
||||
/// </returns>
|
||||
public Task WriteAsync(bool value) => Task.Run(() => { Write(value); });
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified value. 0 for low, any other value for high
|
||||
/// This method performs a digital write
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public void Write(int value) => Write(value != 0 ? GpioPinValue.High : GpioPinValue.Low);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified value. 0 for low, any other value for high
|
||||
/// This method performs a digital write
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteAsync(int value) => Task.Run(() => { Write(value); });
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified value as an analog level.
|
||||
/// You will need to register additional analog modules to enable this function for devices such as the Gertboard.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public void WriteLevel(int value)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Output)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to write to pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Writes are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Output}");
|
||||
}
|
||||
|
||||
WiringPi.AnalogWrite(PinNumber, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified value as an analog level.
|
||||
/// You will need to register additional analog modules to enable this function for devices such as the Gertboard.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteLevelAsync(int value) => Task.Run(() => { WriteLevel(value); });
|
||||
|
||||
#endregion
|
||||
|
||||
#region Input Mode (Read) Members
|
||||
|
||||
/// <summary>
|
||||
/// Wait for specific pin status
|
||||
/// </summary>
|
||||
/// <param name="status">status to check</param>
|
||||
/// <param name="timeOutMillisecond">timeout to reach status</param>
|
||||
/// <returns>true/false</returns>
|
||||
public bool WaitForValue(GpioPinValue status, int timeOutMillisecond)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Input)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to read from pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}");
|
||||
}
|
||||
|
||||
var hrt = new HighResolutionTimer();
|
||||
hrt.Start();
|
||||
do
|
||||
{
|
||||
if (ReadValue() == status)
|
||||
return true;
|
||||
|
||||
Pi.Timing.SleepMicroseconds(101); // 101 uses nanosleep as opposed to a loop.
|
||||
}
|
||||
while (hrt.ElapsedMilliseconds <= timeOutMillisecond);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the digital value on the pin as a boolean value.
|
||||
/// </summary>
|
||||
/// <returns>The state of the pin</returns>
|
||||
public bool Read()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Input && PinMode != GpioPinDriveMode.Output)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to read from pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input} or {GpioPinDriveMode.Output}");
|
||||
}
|
||||
|
||||
return WiringPi.DigitalRead(PinNumber) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the digital value on the pin as a boolean value.
|
||||
/// </summary>
|
||||
/// <returns>The state of the pin</returns>
|
||||
public Task<bool> ReadAsync() => Task.Run(() => Read());
|
||||
|
||||
/// <summary>
|
||||
/// Reads the digital value on the pin as a High or Low value.
|
||||
/// </summary>
|
||||
/// <returns>The state of the pin</returns>
|
||||
public GpioPinValue ReadValue()
|
||||
=> Read() ? GpioPinValue.High : GpioPinValue.Low;
|
||||
|
||||
/// <summary>
|
||||
/// Reads the digital value on the pin as a High or Low value.
|
||||
/// </summary>
|
||||
/// <returns>The state of the pin</returns>
|
||||
public Task<GpioPinValue> ReadValueAsync() => Task.Run(() => ReadValue());
|
||||
|
||||
/// <summary>
|
||||
/// Reads the analog value on the pin.
|
||||
/// This returns the value read on the supplied analog input pin. You will need to register
|
||||
/// additional analog modules to enable this function for devices such as the Gertboard,
|
||||
/// quick2Wire analog board, etc.
|
||||
/// </summary>
|
||||
/// <returns>The analog level</returns>
|
||||
/// <exception cref="InvalidOperationException">When the pin mode is not configured as an input.</exception>
|
||||
public int ReadLevel()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (PinMode != GpioPinDriveMode.Input)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to read from pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Reads are only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}");
|
||||
}
|
||||
|
||||
return WiringPi.AnalogRead(PinNumber);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the analog value on the pin.
|
||||
/// This returns the value read on the supplied analog input pin. You will need to register
|
||||
/// additional analog modules to enable this function for devices such as the Gertboard,
|
||||
/// quick2Wire analog board, etc.
|
||||
/// </summary>
|
||||
/// <returns>The analog level</returns>
|
||||
public Task<int> ReadLevelAsync() => Task.Run(() => ReadLevel());
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interrupts
|
||||
|
||||
/// <summary>
|
||||
/// Registers the interrupt callback on the pin. Pin mode has to be set to Input.
|
||||
/// </summary>
|
||||
/// <param name="edgeDetection">The edge detection.</param>
|
||||
/// <param name="callback">The callback.</param>
|
||||
/// <exception cref="ArgumentException">callback</exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// An interrupt callback was already registered.
|
||||
/// or
|
||||
/// RegisterInterruptCallback
|
||||
/// </exception>
|
||||
public void RegisterInterruptCallback(EdgeDetection edgeDetection, InterruptServiceRoutineCallback callback)
|
||||
{
|
||||
if (callback == null)
|
||||
throw new ArgumentException($"{nameof(callback)} cannot be null");
|
||||
|
||||
if (InterruptCallback != null)
|
||||
throw new InvalidOperationException("An interrupt callback was already registered.");
|
||||
|
||||
if (PinMode != GpioPinDriveMode.Input)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Unable to {nameof(RegisterInterruptCallback)} for pin {PinNumber} because operating mode is {PinMode}."
|
||||
+ $" Calling {nameof(RegisterInterruptCallback)} is only allowed if {nameof(PinMode)} is set to {GpioPinDriveMode.Input}");
|
||||
}
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
var registerResult = WiringPi.WiringPiISR(PinNumber, (int)edgeDetection, callback);
|
||||
if (registerResult == 0)
|
||||
{
|
||||
InterruptEdgeDetection = edgeDetection;
|
||||
InterruptCallback = callback;
|
||||
}
|
||||
else
|
||||
{
|
||||
HardwareException.Throw(nameof(GpioPin), nameof(RegisterInterruptCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
93
Unosquare.RaspberryIO/Gpio/I2CBus.cs
Normal file
93
Unosquare.RaspberryIO/Gpio/I2CBus.cs
Normal file
@ -0,0 +1,93 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using Native;
|
||||
using Swan.Abstractions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// A simple wrapper for the I2c bus on the Raspberry Pi
|
||||
/// </summary>
|
||||
public class I2CBus : SingletonBase<I2CBus>
|
||||
{
|
||||
// TODO: It would be nice to integrate i2c device detection.
|
||||
private static readonly object SyncRoot = new object();
|
||||
private readonly Dictionary<int, I2CDevice> _devices = new Dictionary<int, I2CDevice>();
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="I2CBus"/> class from being created.
|
||||
/// </summary>
|
||||
private I2CBus()
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the registered devices as a read only collection.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<I2CDevice> Devices => new ReadOnlyCollection<I2CDevice>(_devices.Values.ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="I2CDevice"/> with the specified device identifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The <see cref="I2CDevice"/>.
|
||||
/// </value>
|
||||
/// <param name="deviceId">The device identifier.</param>
|
||||
/// <returns>A reference to an I2C device</returns>
|
||||
public I2CDevice this[int deviceId] => GetDeviceById(deviceId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device by identifier.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The device identifier.</param>
|
||||
/// <returns>The device reference</returns>
|
||||
public I2CDevice GetDeviceById(int deviceId)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return _devices[deviceId];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a device to the bus by its Id. If the device is already registered it simply returns the existing device.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The device identifier.</param>
|
||||
/// <returns>The device reference</returns>
|
||||
/// <exception cref="KeyNotFoundException">When the device file descriptor is not found</exception>
|
||||
public I2CDevice AddDevice(int deviceId)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (_devices.ContainsKey(deviceId))
|
||||
return _devices[deviceId];
|
||||
|
||||
var fileDescriptor = SetupFileDescriptor(deviceId);
|
||||
if (fileDescriptor < 0)
|
||||
throw new KeyNotFoundException($"Device with id {deviceId} could not be registered with the I2C bus. Error Code: {fileDescriptor}.");
|
||||
|
||||
var device = new I2CDevice(deviceId, fileDescriptor);
|
||||
_devices[deviceId] = device;
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This initializes the I2C system with your given device identifier.
|
||||
/// The ID is the I2C number of the device and you can use the i2cdetect program to find this out.
|
||||
/// wiringPiI2CSetup() will work out which revision Raspberry Pi you have and open the appropriate device in /dev.
|
||||
/// The return value is the standard Linux filehandle, or -1 if any error – in which case, you can consult errno as usual.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The device identifier.</param>
|
||||
/// <returns>The Linux file handle</returns>
|
||||
private static int SetupFileDescriptor(int deviceId)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
return WiringPi.WiringPiI2CSetup(deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
195
Unosquare.RaspberryIO/Gpio/I2CDevice.cs
Normal file
195
Unosquare.RaspberryIO/Gpio/I2CDevice.cs
Normal file
@ -0,0 +1,195 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Native;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a device on the I2C Bus
|
||||
/// </summary>
|
||||
public class I2CDevice
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="I2CDevice"/> class.
|
||||
/// </summary>
|
||||
/// <param name="deviceId">The device identifier.</param>
|
||||
/// <param name="fileDescriptor">The file descriptor.</param>
|
||||
internal I2CDevice(int deviceId, int fileDescriptor)
|
||||
{
|
||||
DeviceId = deviceId;
|
||||
FileDescriptor = fileDescriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device identifier.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The device identifier.
|
||||
/// </value>
|
||||
public int DeviceId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the standard POSIX file descriptor.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The file descriptor.
|
||||
/// </value>
|
||||
public int FileDescriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from the specified file descriptor
|
||||
/// </summary>
|
||||
/// <returns>The byte from device</returns>
|
||||
public byte Read()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CRead(FileDescriptor);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Read));
|
||||
return (byte)result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte from the specified file descriptor
|
||||
/// </summary>
|
||||
/// <returns>The byte from device</returns>
|
||||
public Task<byte> ReadAsync() => Task.Run(() => Read());
|
||||
|
||||
/// <summary>
|
||||
/// Reads a buffer of the specified length, one byte at a time
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <returns>The byte array from device</returns>
|
||||
public byte[] Read(int length)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var buffer = new byte[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CRead(FileDescriptor);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Read));
|
||||
buffer[i] = (byte)result;
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a buffer of the specified length, one byte at a time
|
||||
/// </summary>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <returns>The byte array from device</returns>
|
||||
public Task<byte[]> ReadAsync(int length) => Task.Run(() => Read(length));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data the specified file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
public void Write(byte data)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CWrite(FileDescriptor, data);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Write));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte of data the specified file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteAsync(byte data) => Task.Run(() => { Write(data); });
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of bytes to the specified file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
public void Write(byte[] data)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
foreach (var b in data)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CWrite(FileDescriptor, b);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(Write));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of bytes to the specified file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteAsync(byte[] data)
|
||||
{
|
||||
return Task.Run(() => { Write(data); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These write an 8 or 16-bit data value into the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="address">The register.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
public void WriteAddressByte(int address, byte data)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CWriteReg8(FileDescriptor, address, data);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressByte));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These write an 8 or 16-bit data value into the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="address">The register.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
public void WriteAddressWord(int address, ushort data)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CWriteReg16(FileDescriptor, address, data);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(WriteAddressWord));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These read an 8 or 16-bit value from the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="address">The register.</param>
|
||||
/// <returns>The address byte from device</returns>
|
||||
public byte ReadAddressByte(int address)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CReadReg8(FileDescriptor, address);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressByte));
|
||||
|
||||
return (byte)result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These read an 8 or 16-bit value from the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="address">The register.</param>
|
||||
/// <returns>The address word from device</returns>
|
||||
public ushort ReadAddressWord(int address)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = WiringPi.WiringPiI2CReadReg16(FileDescriptor, address);
|
||||
if (result < 0) HardwareException.Throw(nameof(I2CDevice), nameof(ReadAddressWord));
|
||||
|
||||
return Convert.ToUInt16(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
Unosquare.RaspberryIO/Gpio/SpiBus.cs
Normal file
72
Unosquare.RaspberryIO/Gpio/SpiBus.cs
Normal file
@ -0,0 +1,72 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using Swan.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// The SPI Bus containing the 2 SPI channels
|
||||
/// </summary>
|
||||
public class SpiBus : SingletonBase<SpiBus>
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="SpiBus"/> class from being created.
|
||||
/// </summary>
|
||||
private SpiBus()
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
#region SPI Access
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel 0 frequency in Hz.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The channel0 frequency.
|
||||
/// </value>
|
||||
public int Channel0Frequency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SPI bus on channel 1.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The channel0.
|
||||
/// </value>
|
||||
public SpiChannel Channel0
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Channel0Frequency == 0)
|
||||
Channel0Frequency = SpiChannel.DefaultFrequency;
|
||||
|
||||
return SpiChannel.Retrieve(SpiChannelNumber.Channel0, Channel0Frequency);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel 1 frequency in Hz
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The channel1 frequency.
|
||||
/// </value>
|
||||
public int Channel1Frequency { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SPI bus on channel 1.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The channel1.
|
||||
/// </value>
|
||||
public SpiChannel Channel1
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Channel1Frequency == 0)
|
||||
Channel1Frequency = SpiChannel.DefaultFrequency;
|
||||
|
||||
return SpiChannel.Retrieve(SpiChannelNumber.Channel1, Channel1Frequency);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
154
Unosquare.RaspberryIO/Gpio/SpiChannel.cs
Normal file
154
Unosquare.RaspberryIO/Gpio/SpiChannel.cs
Normal file
@ -0,0 +1,154 @@
|
||||
namespace Unosquare.RaspberryIO.Gpio
|
||||
{
|
||||
using Native;
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to using the SPI buses on the GPIO.
|
||||
/// SPI is a bus that works like a ring shift register
|
||||
/// The number of bytes pushed is equal to the number of bytes received.
|
||||
/// </summary>
|
||||
public sealed class SpiChannel
|
||||
{
|
||||
/// <summary>
|
||||
/// The minimum frequency of an SPI Channel
|
||||
/// </summary>
|
||||
public const int MinFrequency = 500000;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum frequency of an SPI channel
|
||||
/// </summary>
|
||||
public const int MaxFrequency = 32000000;
|
||||
|
||||
/// <summary>
|
||||
/// The default frequency of SPI channels
|
||||
/// This is set to 8 Mhz wich is typical in modern hardware.
|
||||
/// </summary>
|
||||
public const int DefaultFrequency = 8000000;
|
||||
|
||||
private static readonly object SyncRoot = new object();
|
||||
private static readonly Dictionary<SpiChannelNumber, SpiChannel> Buses = new Dictionary<SpiChannelNumber, SpiChannel>();
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SpiChannel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <param name="frequency">The frequency.</param>
|
||||
private SpiChannel(SpiChannelNumber channel, int frequency)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
Frequency = frequency.Clamp(MinFrequency, MaxFrequency);
|
||||
Channel = (int)channel;
|
||||
FileDescriptor = WiringPi.WiringPiSPISetup((int)channel, Frequency);
|
||||
|
||||
if (FileDescriptor < 0)
|
||||
{
|
||||
HardwareException.Throw(nameof(SpiChannel), channel.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the standard initialization file descriptor.
|
||||
/// anything negative means error.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The file descriptor.
|
||||
/// </value>
|
||||
public int FileDescriptor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel.
|
||||
/// </summary>
|
||||
public int Channel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the frequency.
|
||||
/// </summary>
|
||||
public int Frequency { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sends data and simultaneously receives the data in the return buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>The read bytes from the ring-style bus</returns>
|
||||
public byte[] SendReceive(byte[] buffer)
|
||||
{
|
||||
if (buffer == null || buffer.Length == 0)
|
||||
return null;
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
var spiBuffer = new byte[buffer.Length];
|
||||
Array.Copy(buffer, spiBuffer, buffer.Length);
|
||||
|
||||
var result = WiringPi.WiringPiSPIDataRW(Channel, spiBuffer, spiBuffer.Length);
|
||||
if (result < 0) HardwareException.Throw(nameof(SpiChannel), nameof(SendReceive));
|
||||
|
||||
return spiBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends data and simultaneously receives the data in the return buffer
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>
|
||||
/// The read bytes from the ring-style bus
|
||||
/// </returns>
|
||||
public Task<byte[]> SendReceiveAsync(byte[] buffer) => Task.Run(() => SendReceive(buffer));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified buffer the the underlying FileDescriptor.
|
||||
/// Do not use this method if you expect data back.
|
||||
/// This method is efficient if used in a fire-and-forget scenario
|
||||
/// like sending data over to those long RGB LED strips
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
public void Write(byte[] buffer)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var result = Standard.Write(FileDescriptor, buffer, buffer.Length);
|
||||
|
||||
if (result < 0)
|
||||
HardwareException.Throw(nameof(SpiChannel), nameof(Write));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified buffer the the underlying FileDescriptor.
|
||||
/// Do not use this method if you expect data back.
|
||||
/// This method is efficient if used in a fire-and-forget scenario
|
||||
/// like sending data over to those long RGB LED strips
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>The awaitable task</returns>
|
||||
public Task WriteAsync(byte[] buffer) => Task.Run(() => { Write(buffer); });
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the spi bus. If the bus channel is not registered it sets it up automatically.
|
||||
/// If it had been previously registered, then the bus is simply returned.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <param name="frequency">The frequency.</param>
|
||||
/// <returns>The usable SPI channel</returns>
|
||||
internal static SpiChannel Retrieve(SpiChannelNumber channel, int frequency)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
if (Buses.ContainsKey(channel))
|
||||
return Buses[channel];
|
||||
|
||||
var newBus = new SpiChannel(channel, frequency);
|
||||
Buses[channel] = newBus;
|
||||
return newBus;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
Unosquare.RaspberryIO/Native/Delegates.cs
Normal file
12
Unosquare.RaspberryIO/Native/Delegates.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate defining a callback for an Interrupt Service Routine
|
||||
/// </summary>
|
||||
public delegate void InterruptServiceRoutineCallback();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the body of a thread worker
|
||||
/// </summary>
|
||||
public delegate void ThreadWorker();
|
||||
}
|
73
Unosquare.RaspberryIO/Native/HardwareException.cs
Normal file
73
Unosquare.RaspberryIO/Native/HardwareException.cs
Normal file
@ -0,0 +1,73 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a low-level exception, typically thrown when return codes from a
|
||||
/// low-level operation is non-zero or in some cases when it is less than zero.
|
||||
/// </summary>
|
||||
/// <seealso cref="Exception" />
|
||||
public class HardwareException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HardwareException" /> class.
|
||||
/// </summary>
|
||||
/// <param name="errorCode">The error code.</param>
|
||||
/// <param name="component">The component.</param>
|
||||
public HardwareException(int errorCode, string component)
|
||||
: base($"A hardware exception occurred. Error Code: {errorCode}")
|
||||
{
|
||||
ExtendedMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
ExtendedMessage = Standard.Strerror(errorCode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: strerror not working great...
|
||||
$"Could not retrieve native error description using {nameof(Standard.Strerror)}".Error(Pi.LoggerSource);
|
||||
}
|
||||
|
||||
ErrorCode = errorCode;
|
||||
Component = component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error code.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error code.
|
||||
/// </value>
|
||||
public int ErrorCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The component.
|
||||
/// </value>
|
||||
public string Component { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extended message (could be null).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The extended message.
|
||||
/// </value>
|
||||
public string ExtendedMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Throws a new instance of a hardware error by retrieving the last error number (errno).
|
||||
/// </summary>
|
||||
/// <param name="className">Name of the class.</param>
|
||||
/// <param name="methodName">Name of the method.</param>
|
||||
/// <exception cref="HardwareException">When an error thrown by an API call occurs</exception>
|
||||
public static void Throw(string className, string methodName) => throw new HardwareException(Marshal.GetLastWin32Error(), $"{className}.{methodName}");
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => $"{GetType()}{(string.IsNullOrWhiteSpace(Component) ? string.Empty : $" on {Component}")}: ({ErrorCode}) - {Message}";
|
||||
}
|
||||
}
|
32
Unosquare.RaspberryIO/Native/HighResolutionTimer.cs
Normal file
32
Unosquare.RaspberryIO/Native/HighResolutionTimer.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to a high- esolution, time measuring device.
|
||||
/// </summary>
|
||||
/// <seealso cref="Stopwatch" />
|
||||
public class HighResolutionTimer : Stopwatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HighResolutionTimer"/> class.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">High-resolution timer not available</exception>
|
||||
public HighResolutionTimer()
|
||||
{
|
||||
if (!IsHighResolution)
|
||||
throw new NotSupportedException("High-resolution timer not available");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the numer of microseconds per timer tick.
|
||||
/// </summary>
|
||||
public static double MicrosecondsPerTick { get; } = 1000000d / Frequency;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the elapsed microseconds.
|
||||
/// </summary>
|
||||
public long ElapsedMicroseconds => (long)(ElapsedTicks * MicrosecondsPerTick);
|
||||
}
|
||||
}
|
84
Unosquare.RaspberryIO/Native/Standard.cs
Normal file
84
Unosquare.RaspberryIO/Native/Standard.cs
Normal file
@ -0,0 +1,84 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Provides standard libc calls using platform-invoke
|
||||
/// </summary>
|
||||
internal static class Standard
|
||||
{
|
||||
internal const string LibCLibrary = "libc";
|
||||
|
||||
#region LibC Calls
|
||||
|
||||
/// <summary>
|
||||
/// Strerrors the specified error.
|
||||
/// </summary>
|
||||
/// <param name="error">The error.</param>
|
||||
/// <returns></returns>
|
||||
public static string Strerror(int error)
|
||||
{
|
||||
if (!Runtime.IsUsingMonoRuntime) return StrError(error);
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new StringBuilder(256);
|
||||
var result = Strerror(error, buffer, (ulong)buffer.Capacity);
|
||||
return (result != -1) ? buffer.ToString() : null;
|
||||
}
|
||||
catch (EntryPointNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes file permissions on a Unix file system
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(LibCLibrary, EntryPoint = "chmod", SetLastError = true)]
|
||||
public static extern int Chmod(string filename, uint mode);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to a 32 bit integer. Use endpointer as IntPtr.Zero
|
||||
/// </summary>
|
||||
/// <param name="numberString">The number string.</param>
|
||||
/// <param name="endPointer">The end pointer.</param>
|
||||
/// <param name="numberBase">The number base.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(LibCLibrary, EntryPoint = "strtol", SetLastError = true)]
|
||||
public static extern int StringToInteger(string numberString, IntPtr endPointer, int numberBase);
|
||||
|
||||
/// <summary>
|
||||
/// The write() function attempts to write nbytes from buffer to the file associated with handle. On text files, it expands each LF to a CR/LF.
|
||||
/// The function returns the number of bytes written to the file. A return value of -1 indicates an error, with errno set appropriately.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="count">The count.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(LibCLibrary, EntryPoint = "write", SetLastError = true)]
|
||||
public static extern int Write(int fd, byte[] buffer, int count);
|
||||
|
||||
/// <summary>
|
||||
/// Fills in the structure with information about the system.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(LibCLibrary, EntryPoint = "uname", SetLastError = true)]
|
||||
public static extern int Uname(out SystemName name);
|
||||
|
||||
[DllImport(LibCLibrary, EntryPoint = "strerror", SetLastError = true)]
|
||||
private static extern string StrError(int errnum);
|
||||
|
||||
[DllImport("MonoPosixHelper", EntryPoint = "Mono_Posix_Syscall_strerror_r", SetLastError = true)]
|
||||
private static extern int Strerror(int error, [Out] StringBuilder buffer, ulong length);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
47
Unosquare.RaspberryIO/Native/SystemName.cs
Normal file
47
Unosquare.RaspberryIO/Native/SystemName.cs
Normal file
@ -0,0 +1,47 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// OS uname structure
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
internal struct SystemName
|
||||
{
|
||||
/// <summary>
|
||||
/// System name
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string SysName;
|
||||
|
||||
/// <summary>
|
||||
/// Node name
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string NodeName;
|
||||
|
||||
/// <summary>
|
||||
/// Release level
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string Release;
|
||||
|
||||
/// <summary>
|
||||
/// Version level
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string Version;
|
||||
|
||||
/// <summary>
|
||||
/// Hardware level
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string Machine;
|
||||
|
||||
/// <summary>
|
||||
/// Domain name
|
||||
/// </summary>
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 65)]
|
||||
public string DomainName;
|
||||
}
|
||||
}
|
28
Unosquare.RaspberryIO/Native/ThreadLockKey.cs
Normal file
28
Unosquare.RaspberryIO/Native/ThreadLockKey.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the different threading locking keys
|
||||
/// </summary>
|
||||
public enum ThreadLockKey
|
||||
{
|
||||
/// <summary>
|
||||
/// The lock 0
|
||||
/// </summary>
|
||||
Lock0 = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The lock 1
|
||||
/// </summary>
|
||||
Lock1 = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The lock 2
|
||||
/// </summary>
|
||||
Lock2 = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The lock 3
|
||||
/// </summary>
|
||||
Lock3 = 3,
|
||||
}
|
||||
}
|
108
Unosquare.RaspberryIO/Native/Timing.cs
Normal file
108
Unosquare.RaspberryIO/Native/Timing.cs
Normal file
@ -0,0 +1,108 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using Swan;
|
||||
using Swan.Abstractions;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to timing and threading properties and methods
|
||||
/// </summary>
|
||||
public class Timing : SingletonBase<Timing>
|
||||
{
|
||||
/// <summary>
|
||||
/// Prevents a default instance of the <see cref="Timing"/> class from being created.
|
||||
/// </summary>
|
||||
/// <exception cref="NotSupportedException">Could not initialize the GPIO controller</exception>
|
||||
private Timing()
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns a number representing the number of milliseconds since your program
|
||||
/// initialized the GPIO controller.
|
||||
/// It returns an unsigned 32-bit number which wraps after 49 days.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The milliseconds since setup.
|
||||
/// </value>
|
||||
public uint MillisecondsSinceSetup => WiringPi.Millis();
|
||||
|
||||
/// <summary>
|
||||
/// This returns a number representing the number of microseconds since your
|
||||
/// program initialized the GPIO controller
|
||||
/// It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The microseconds since setup.
|
||||
/// </value>
|
||||
public uint MicrosecondsSinceSetup => WiringPi.Micros();
|
||||
|
||||
/// <summary>
|
||||
/// This causes program execution to pause for at least howLong milliseconds.
|
||||
/// Due to the multi-tasking nature of Linux it could be longer.
|
||||
/// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public static void SleepMilliseconds(uint value) => WiringPi.Delay(value);
|
||||
|
||||
/// <summary>
|
||||
/// This causes program execution to pause for at least howLong microseconds.
|
||||
/// Due to the multi-tasking nature of Linux it could be longer.
|
||||
/// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes.
|
||||
/// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time,
|
||||
/// Delays over 100 microseconds are done using the system nanosleep() function –
|
||||
/// You may need to consider the implications of very short delays on the overall performance of the system,
|
||||
/// especially if using threads.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
public void SleepMicroseconds(uint value) => WiringPi.DelayMicroseconds(value);
|
||||
|
||||
/// <summary>
|
||||
/// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority and
|
||||
/// enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum).
|
||||
/// This won’t make your program go any faster, but it will give it a bigger slice of time when other programs
|
||||
/// are running. The priority parameter works relative to others – so you can make one program priority 1 and
|
||||
/// another priority 2 and it will have the same effect as setting one to 10 and the other to 90
|
||||
/// (as long as no other programs are running with elevated priorities)
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority.</param>
|
||||
public void SetThreadPriority(int priority)
|
||||
{
|
||||
priority = priority.Clamp(0, 99);
|
||||
var result = WiringPi.PiHiPri(priority);
|
||||
if (result < 0) HardwareException.Throw(nameof(Timing), nameof(SetThreadPriority));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is really nothing more than a simplified interface to the Posix threads mechanism that Linux supports.
|
||||
/// See the manual pages on Posix threads (man pthread) if you need more control over them.
|
||||
/// </summary>
|
||||
/// <param name="worker">The worker.</param>
|
||||
/// <exception cref="ArgumentNullException">worker</exception>
|
||||
public void CreateThread(ThreadWorker worker)
|
||||
{
|
||||
if (worker == null)
|
||||
throw new ArgumentNullException(nameof(worker));
|
||||
|
||||
var result = WiringPi.PiThreadCreate(worker);
|
||||
if (result != 0) HardwareException.Throw(nameof(Timing), nameof(CreateThread));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// These allow you to synchronize variable updates from your main program to any threads running in your program.
|
||||
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key,
|
||||
/// it will be stalled until the first process has unlocked the same key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
public void Lock(ThreadLockKey key) => WiringPi.PiLock((int)key);
|
||||
|
||||
/// <summary>
|
||||
/// These allow you to synchronize variable updates from your main program to any threads running in your program.
|
||||
/// keyNum is a number from 0 to 3 and represents a “key”. When another process tries to lock the same key,
|
||||
/// it will be stalled until the first process has unlocked the same key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
public void Unlock(ThreadLockKey key) => WiringPi.PiUnlock((int)key);
|
||||
}
|
||||
}
|
79
Unosquare.RaspberryIO/Native/WiringPi.I2C.cs
Normal file
79
Unosquare.RaspberryIO/Native/WiringPi.I2C.cs
Normal file
@ -0,0 +1,79 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public partial class WiringPi
|
||||
{
|
||||
#region WiringPi - I2C Library Calls
|
||||
|
||||
/// <summary>
|
||||
/// Simple device read. Some devices present data when you read them without having to do any register transactions.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CRead", SetLastError = true)]
|
||||
public static extern int WiringPiI2CRead(int fd);
|
||||
|
||||
/// <summary>
|
||||
/// These read an 8-bit value from the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="reg">The reg.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg8", SetLastError = true)]
|
||||
public static extern int WiringPiI2CReadReg8(int fd, int reg);
|
||||
|
||||
/// <summary>
|
||||
/// These read a 16-bit value from the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="reg">The reg.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CReadReg16", SetLastError = true)]
|
||||
public static extern int WiringPiI2CReadReg16(int fd, int reg);
|
||||
|
||||
/// <summary>
|
||||
/// Simple device write. Some devices accept data this way without needing to access any internal registers.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWrite", SetLastError = true)]
|
||||
public static extern int WiringPiI2CWrite(int fd, int data);
|
||||
|
||||
/// <summary>
|
||||
/// These write an 8-bit data value into the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="reg">The reg.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg8", SetLastError = true)]
|
||||
public static extern int WiringPiI2CWriteReg8(int fd, int reg, int data);
|
||||
|
||||
/// <summary>
|
||||
/// These write a 16-bit data value into the device register indicated.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="reg">The reg.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CWriteReg16", SetLastError = true)]
|
||||
public static extern int WiringPiI2CWriteReg16(int fd, int reg, int data);
|
||||
|
||||
/// <summary>
|
||||
/// This initialises the I2C system with your given device identifier.
|
||||
/// The ID is the I2C number of the device and you can use the i2cdetect program to find this out. wiringPiI2CSetup()
|
||||
/// will work out which revision Raspberry Pi you have and open the appropriate device in /dev.
|
||||
/// The return value is the standard Linux filehandle, or -1 if any error – in which case, you can consult errno as usual.
|
||||
/// E.g. the popular MCP23017 GPIO expander is usually device Id 0x20, so this is the number you would pass into wiringPiI2CSetup().
|
||||
/// </summary>
|
||||
/// <param name="devId">The dev identifier.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiI2CSetup", SetLastError = true)]
|
||||
public static extern int WiringPiI2CSetup(int devId);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
73
Unosquare.RaspberryIO/Native/WiringPi.SerialPort.cs
Normal file
73
Unosquare.RaspberryIO/Native/WiringPi.SerialPort.cs
Normal file
@ -0,0 +1,73 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public partial class WiringPi
|
||||
{
|
||||
#region WiringPi - Serial Port
|
||||
|
||||
/// <summary>
|
||||
/// This opens and initialises the serial device and sets the baud rate. It sets the port into “raw” mode (character at a time and no translations),
|
||||
/// and sets the read timeout to 10 seconds. The return value is the file descriptor or -1 for any error, in which case errno will be set as appropriate.
|
||||
/// The wiringSerial library is intended to provide simplified control – suitable for most applications, however if you need advanced control
|
||||
/// – e.g. parity control, modem control lines (via a USB adapter, there are none on the Pi’s on-board UART!) and so on,
|
||||
/// then you need to do some of this the old fashioned way.
|
||||
/// </summary>
|
||||
/// <param name="device">The device.</param>
|
||||
/// <param name="baud">The baud.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialOpen", SetLastError = true)]
|
||||
public static extern int SerialOpen(string device, int baud);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the device identified by the file descriptor given.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialClose", SetLastError = true)]
|
||||
public static extern int SerialClose(int fd);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the single byte to the serial device identified by the given file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="c">The c.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialPutchar", SetLastError = true)]
|
||||
public static extern void SerialPutchar(int fd, byte c);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the nul-terminated string to the serial device identified by the given file descriptor.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <param name="s">The s.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialPuts", SetLastError = true)]
|
||||
public static extern void SerialPuts(int fd, string s);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of characters available for reading, or -1 for any error condition,
|
||||
/// in which case errno will be set appropriately.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialDataAvail", SetLastError = true)]
|
||||
public static extern int SerialDataAvail(int fd);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next character available on the serial device.
|
||||
/// This call will block for up to 10 seconds if no data is available (when it will return -1)
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialGetchar", SetLastError = true)]
|
||||
public static extern int SerialGetchar(int fd);
|
||||
|
||||
/// <summary>
|
||||
/// This discards all data received, or waiting to be send down the given device.
|
||||
/// </summary>
|
||||
/// <param name="fd">The fd.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "serialFlush", SetLastError = true)]
|
||||
public static extern void SerialFlush(int fd);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
36
Unosquare.RaspberryIO/Native/WiringPi.Shift.cs
Normal file
36
Unosquare.RaspberryIO/Native/WiringPi.Shift.cs
Normal file
@ -0,0 +1,36 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public partial class WiringPi
|
||||
{
|
||||
#region WiringPi - Shift Library
|
||||
|
||||
/// <summary>
|
||||
/// This shifts an 8-bit data value in with the data appearing on the dPin and the clock being sent out on the cPin.
|
||||
/// Order is either LSBFIRST or MSBFIRST. The data is sampled after the cPin goes high.
|
||||
/// (So cPin high, sample data, cPin low, repeat for 8 bits) The 8-bit value is returned by the function.
|
||||
/// </summary>
|
||||
/// <param name="dPin">The d pin.</param>
|
||||
/// <param name="cPin">The c pin.</param>
|
||||
/// <param name="order">The order.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "shiftIn", SetLastError = true)]
|
||||
public static extern byte ShiftIn(byte dPin, byte cPin, byte order);
|
||||
|
||||
/// <summary>
|
||||
/// The shifts an 8-bit data value val out with the data being sent out on dPin and the clock being sent out on the cPin.
|
||||
/// order is as above. Data is clocked out on the rising or falling edge – ie. dPin is set, then cPin is taken high then low
|
||||
/// – repeated for the 8 bits.
|
||||
/// </summary>
|
||||
/// <param name="dPin">The d pin.</param>
|
||||
/// <param name="cPin">The c pin.</param>
|
||||
/// <param name="order">The order.</param>
|
||||
/// <param name="val">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "shiftOut", SetLastError = true)]
|
||||
public static extern void ShiftOut(byte dPin, byte cPin, byte order, byte val);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
64
Unosquare.RaspberryIO/Native/WiringPi.SoftPwm.cs
Normal file
64
Unosquare.RaspberryIO/Native/WiringPi.SoftPwm.cs
Normal file
@ -0,0 +1,64 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public partial class WiringPi
|
||||
{
|
||||
#region WiringPi - Soft PWM (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/softPwm.h)
|
||||
|
||||
/// <summary>
|
||||
/// This creates a software controlled PWM pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup()
|
||||
/// function you used. Use 100 for the pwmRange, then the value can be anything from 0 (off) to 100 (fully on) for the given pin.
|
||||
/// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
/// <param name="pwmRange">The PWM range.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softPwmCreate", SetLastError = true)]
|
||||
public static extern int SoftPwmCreate(int pin, int initialValue, int pwmRange);
|
||||
|
||||
/// <summary>
|
||||
/// This updates the PWM value on the given pin. The value is checked to be in-range and pins that haven’t previously
|
||||
/// been initialized via softPwmCreate will be silently ignored.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softPwmWrite", SetLastError = true)]
|
||||
public static extern void SoftPwmWrite(int pin, int value);
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softPwmStop", SetLastError = true)]
|
||||
public static extern void SoftPwmStop(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// This creates a software controlled tone pin. You can use any GPIO pin and the pin numbering will be that of the wiringPiSetup() function you used.
|
||||
/// The return value is 0 for success. Anything else and you should check the global errno variable to see what went wrong.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softToneCreate", SetLastError = true)]
|
||||
public static extern int SoftToneCreate(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softToneStop", SetLastError = true)]
|
||||
public static extern void SoftToneStop(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// This updates the tone frequency value on the given pin. The tone will be played until you set the frequency to 0.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="freq">The freq.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "softToneWrite", SetLastError = true)]
|
||||
public static extern void SoftToneWrite(int pin, int freq);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
53
Unosquare.RaspberryIO/Native/WiringPi.Spi.cs
Normal file
53
Unosquare.RaspberryIO/Native/WiringPi.Spi.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public partial class WiringPi
|
||||
{
|
||||
#region WiringPi - SPI Library Calls
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <returns>Unknown</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIGetFd", SetLastError = true)]
|
||||
public static extern int WiringPiSPIGetFd(int channel);
|
||||
|
||||
/// <summary>
|
||||
/// This performs a simultaneous write/read transaction over the selected SPI bus. Data that was in your buffer is overwritten by data returned from the SPI bus.
|
||||
/// That’s all there is in the helper library. It is possible to do simple read and writes over the SPI bus using the standard read() and write() system calls though –
|
||||
/// write() may be better to use for sending data to chains of shift registers, or those LED strings where you send RGB triplets of data.
|
||||
/// Devices such as A/D and D/A converters usually need to perform a concurrent write/read transaction to work.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="len">The length.</param>
|
||||
/// <returns>The result</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPIDataRW", SetLastError = true)]
|
||||
public static extern int WiringPiSPIDataRW(int channel, byte[] data, int len);
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <param name="speed">The speed.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <returns>Unkown</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetupMode", SetLastError = true)]
|
||||
public static extern int WiringPiSPISetupMode(int channel, int speed, int mode);
|
||||
|
||||
/// <summary>
|
||||
/// This is the way to initialize a channel (The Pi has 2 channels; 0 and 1). The speed parameter is an integer
|
||||
/// in the range 500,000 through 32,000,000 and represents the SPI clock speed in Hz.
|
||||
/// The returned value is the Linux file-descriptor for the device, or -1 on error. If an error has happened, you may use the standard errno global variable to see why.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel.</param>
|
||||
/// <param name="speed">The speed.</param>
|
||||
/// <returns>The Linux file descriptor for the device or -1 for error</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSPISetup", SetLastError = true)]
|
||||
public static extern int WiringPiSPISetup(int channel, int speed);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
394
Unosquare.RaspberryIO/Native/WiringPi.cs
Normal file
394
Unosquare.RaspberryIO/Native/WiringPi.cs
Normal file
@ -0,0 +1,394 @@
|
||||
namespace Unosquare.RaspberryIO.Native
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
/// <summary>
|
||||
/// Provides native C WiringPi Library function call wrappers
|
||||
/// All credit for the native library goes to the author of http://wiringpi.com/
|
||||
/// The wrappers were written based on https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h
|
||||
/// </summary>
|
||||
public partial class WiringPi
|
||||
{
|
||||
internal const string WiringPiLibrary = "libwiringPi.so.2.46";
|
||||
|
||||
#region WiringPi - Core Functions (https://github.com/WiringPi/WiringPi/blob/master/wiringPi/wiringPi.h)
|
||||
|
||||
/// <summary>
|
||||
/// This initialises wiringPi and assumes that the calling program is going to be using the wiringPi pin numbering scheme.
|
||||
/// This is a simplified numbering scheme which provides a mapping from virtual pin numbers 0 through 16 to the real underlying Broadcom GPIO pin numbers.
|
||||
/// See the pins page for a table which maps the wiringPi pin number to the Broadcom GPIO pin number to the physical location on the edge connector.
|
||||
/// This function needs to be called with root privileges.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetup", SetLastError = true)]
|
||||
public static extern int WiringPiSetup();
|
||||
|
||||
/// <summary>
|
||||
/// This initialises wiringPi but uses the /sys/class/gpio interface rather than accessing the hardware directly.
|
||||
/// This can be called as a non-root user provided the GPIO pins have been exported before-hand using the gpio program.
|
||||
/// Pin numbering in this mode is the native Broadcom GPIO numbers – the same as wiringPiSetupGpio() above,
|
||||
/// so be aware of the differences between Rev 1 and Rev 2 boards.
|
||||
///
|
||||
/// Note: In this mode you can only use the pins which have been exported via the /sys/class/gpio interface before you run your program.
|
||||
/// You can do this in a separate shell-script, or by using the system() function from inside your program to call the gpio program.
|
||||
/// Also note that some functions have no effect when using this mode as they’re not currently possible to action unless called with root privileges.
|
||||
/// (although you can use system() to call gpio to set/change modes if needed)
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupSys", SetLastError = true)]
|
||||
public static extern int WiringPiSetupSys();
|
||||
|
||||
/// <summary>
|
||||
/// This is identical to wiringPiSetup, however it allows the calling programs to use the Broadcom GPIO
|
||||
/// pin numbers directly with no re-mapping.
|
||||
/// As above, this function needs to be called with root privileges, and note that some pins are different
|
||||
/// from revision 1 to revision 2 boards.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupGpio", SetLastError = true)]
|
||||
public static extern int WiringPiSetupGpio();
|
||||
|
||||
/// <summary>
|
||||
/// Identical to wiringPiSetup, however it allows the calling programs to use the physical pin numbers on the P1 connector only.
|
||||
/// This function needs to be called with root privileges.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiSetupPhys", SetLastError = true)]
|
||||
public static extern int WiringPiSetupPhys();
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pinModeAlt", SetLastError = true)]
|
||||
public static extern void PinModeAlt(int pin, int mode);
|
||||
|
||||
/// <summary>
|
||||
/// This sets the mode of a pin to either INPUT, OUTPUT, PWM_OUTPUT or GPIO_CLOCK.
|
||||
/// Note that only wiringPi pin 1 (BCM_GPIO 18) supports PWM output and only wiringPi pin 7 (BCM_GPIO 4)
|
||||
/// supports CLOCK output modes.
|
||||
///
|
||||
/// This function has no effect when in Sys mode. If you need to change the pin mode, then you can
|
||||
/// do it with the gpio program in a script before you start your program.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pinMode", SetLastError = true)]
|
||||
public static extern void PinMode(int pin, int mode);
|
||||
|
||||
/// <summary>
|
||||
/// This sets the pull-up or pull-down resistor mode on the given pin, which should be set as an input.
|
||||
/// Unlike the Arduino, the BCM2835 has both pull-up an down internal resistors. The parameter pud should be; PUD_OFF,
|
||||
/// (no pull up/down), PUD_DOWN (pull to ground) or PUD_UP (pull to 3.3v) The internal pull up/down resistors
|
||||
/// have a value of approximately 50KΩ on the Raspberry Pi.
|
||||
///
|
||||
/// This function has no effect on the Raspberry Pi’s GPIO pins when in Sys mode.
|
||||
/// If you need to activate a pull-up/pull-down, then you can do it with the gpio program in a script before you start your program.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="pud">The pud.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pullUpDnControl", SetLastError = true)]
|
||||
public static extern void PullUpDnControl(int pin, int pud);
|
||||
|
||||
/// <summary>
|
||||
/// This function returns the value read at the given pin. It will be HIGH or LOW (1 or 0) depending on the logic level at the pin.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalRead", SetLastError = true)]
|
||||
public static extern int DigitalRead(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the value HIGH or LOW (1 or 0) to the given pin which must have been previously set as an output.
|
||||
/// WiringPi treats any non-zero number as HIGH, however 0 is the only representation of LOW.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalWrite", SetLastError = true)]
|
||||
public static extern void DigitalWrite(int pin, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the value to the PWM register for the given pin. The Raspberry Pi has one
|
||||
/// on-board PWM pin, pin 1 (BMC_GPIO 18, Phys 12) and the range is 0-1024.
|
||||
/// Other PWM devices may have other PWM ranges.
|
||||
/// This function is not able to control the Pi’s on-board PWM when in Sys mode.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pwmWrite", SetLastError = true)]
|
||||
public static extern void PwmWrite(int pin, int value);
|
||||
|
||||
/// <summary>
|
||||
/// This returns the value read on the supplied analog input pin. You will need to
|
||||
/// register additional analog modules to enable this function for devices such as the Gertboard, quick2Wire analog board, etc.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "analogRead", SetLastError = true)]
|
||||
public static extern int AnalogRead(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// This writes the given value to the supplied analog pin. You will need to register additional
|
||||
/// analog modules to enable this function for devices such as the Gertboard.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "analogWrite", SetLastError = true)]
|
||||
public static extern void AnalogWrite(int pin, int value);
|
||||
|
||||
/// <summary>
|
||||
/// This returns the board revision of the Raspberry Pi. It will be either 1 or 2. Some of the BCM_GPIO pins changed number and
|
||||
/// function when moving from board revision 1 to 2, so if you are using BCM_GPIO pin numbers, then you need to be aware of the differences.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piBoardRev", SetLastError = true)]
|
||||
public static extern int PiBoardRev();
|
||||
|
||||
/// <summary>
|
||||
/// This function is undocumented
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <param name="mem">The memory.</param>
|
||||
/// <param name="maker">The maker.</param>
|
||||
/// <param name="overVolted">The over volted.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piBoardId", SetLastError = true)]
|
||||
public static extern int PiBoardId(ref int model, ref int mem, ref int maker, ref int overVolted);
|
||||
|
||||
/// <summary>
|
||||
/// This returns the BCM_GPIO pin number of the supplied wiringPi pin. It takes the board revision into account.
|
||||
/// </summary>
|
||||
/// <param name="wPiPin">The w pi pin.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wpiPinToGpio", SetLastError = true)]
|
||||
public static extern int WpiPinToGpio(int wPiPin);
|
||||
|
||||
/// <summary>
|
||||
/// This returns the BCM_GPIO pin number of the supplied physical pin on the P1 connector.
|
||||
/// </summary>
|
||||
/// <param name="physPin">The physical pin.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "physPinToGpio", SetLastError = true)]
|
||||
public static extern int PhysPinToGpio(int physPin);
|
||||
|
||||
/// <summary>
|
||||
/// This sets the “strength” of the pad drivers for a particular group of pins.
|
||||
/// There are 3 groups of pins and the drive strength is from 0 to 7. Do not use this unless you know what you are doing.
|
||||
/// </summary>
|
||||
/// <param name="group">The group.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "setPadDrive", SetLastError = true)]
|
||||
public static extern int SetPadDrive(int group, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented function
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "getAlt", SetLastError = true)]
|
||||
public static extern int GetAlt(int pin);
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented function
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="freq">The freq.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pwmToneWrite", SetLastError = true)]
|
||||
public static extern int PwmToneWrite(int pin, int freq);
|
||||
|
||||
/// <summary>
|
||||
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pi’s GPIO hardware.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte", SetLastError = true)]
|
||||
public static extern void DigitalWriteByte(int value);
|
||||
|
||||
/// <summary>
|
||||
/// This writes the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to set all 8 bits at once to a particular value, although it still takes two write operations to the Pi’s GPIO hardware.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalWriteByte2", SetLastError = true)]
|
||||
public static extern void DigitalWriteByte2(int value);
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented function
|
||||
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to get all 8 bits at once to a particular value.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte", SetLastError = true)]
|
||||
public static extern uint DigitalReadByte();
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented function
|
||||
/// This reads the 8-bit byte supplied to the first 8 GPIO pins.
|
||||
/// It’s the fastest way to get all 8 bits at once to a particular value.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "digitalReadByte2", SetLastError = true)]
|
||||
public static extern uint DigitalReadByte2();
|
||||
|
||||
/// <summary>
|
||||
/// The PWM generator can run in 2 modes – “balanced” and “mark:space”. The mark:space mode is traditional,
|
||||
/// however the default mode in the Pi is “balanced”. You can switch modes by supplying the parameter: PWM_MODE_BAL or PWM_MODE_MS.
|
||||
/// </summary>
|
||||
/// <param name="mode">The mode.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetMode", SetLastError = true)]
|
||||
public static extern void PwmSetMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// This sets the range register in the PWM generator. The default is 1024.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetRange", SetLastError = true)]
|
||||
public static extern void PwmSetRange(uint range);
|
||||
|
||||
/// <summary>
|
||||
/// This sets the divisor for the PWM clock.
|
||||
/// Note: The PWM control functions can not be used when in Sys mode.
|
||||
/// To understand more about the PWM system, you’ll need to read the Broadcom ARM peripherals manual.
|
||||
/// </summary>
|
||||
/// <param name="divisor">The divisor.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "pwmSetClock", SetLastError = true)]
|
||||
public static extern void PwmSetClock(int divisor);
|
||||
|
||||
/// <summary>
|
||||
/// Undocumented function
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="freq">The freq.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "gpioClockSet", SetLastError = true)]
|
||||
public static extern void GpioClockSet(int pin, int freq);
|
||||
|
||||
/// <summary>
|
||||
/// Note: Jan 2013: The waitForInterrupt() function is deprecated – you should use the newer and easier to use wiringPiISR() function below.
|
||||
/// When called, it will wait for an interrupt event to happen on that pin and your program will be stalled. The timeOut parameter is given in milliseconds,
|
||||
/// or can be -1 which means to wait forever.
|
||||
/// The return value is -1 if an error occurred (and errno will be set appropriately), 0 if it timed out, or 1 on a successful interrupt event.
|
||||
/// Before you call waitForInterrupt, you must first initialise the GPIO pin and at present the only way to do this is to use the gpio program, either
|
||||
/// in a script, or using the system() call from inside your program.
|
||||
/// e.g. We want to wait for a falling-edge interrupt on GPIO pin 0, so to setup the hardware, we need to run: gpio edge 0 falling
|
||||
/// before running the program.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="timeout">The timeout.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[Obsolete]
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "waitForInterrupt", SetLastError = true)]
|
||||
public static extern int WaitForInterrupt(int pin, int timeout);
|
||||
|
||||
/// <summary>
|
||||
/// This function registers a function to received interrupts on the specified pin.
|
||||
/// The edgeType parameter is either INT_EDGE_FALLING, INT_EDGE_RISING, INT_EDGE_BOTH or INT_EDGE_SETUP.
|
||||
/// If it is INT_EDGE_SETUP then no initialisation of the pin will happen – it’s assumed that you have already setup the pin elsewhere
|
||||
/// (e.g. with the gpio program), but if you specify one of the other types, then the pin will be exported and initialised as specified.
|
||||
/// This is accomplished via a suitable call to the gpio utility program, so it need to be available.
|
||||
/// The pin number is supplied in the current mode – native wiringPi, BCM_GPIO, physical or Sys modes.
|
||||
/// This function will work in any mode, and does not need root privileges to work.
|
||||
/// The function will be called when the interrupt triggers. When it is triggered, it’s cleared in the dispatcher before calling your function,
|
||||
/// so if a subsequent interrupt fires before you finish your handler, then it won’t be missed. (However it can only track one more interrupt,
|
||||
/// if more than one interrupt fires while one is being handled then they will be ignored)
|
||||
/// This function is run at a high priority (if the program is run using sudo, or as root) and executes concurrently with the main program.
|
||||
/// It has full access to all the global variables, open file handles and so on.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin.</param>
|
||||
/// <param name="mode">The mode.</param>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "wiringPiISR", SetLastError = true)]
|
||||
public static extern int WiringPiISR(int pin, int mode, InterruptServiceRoutineCallback method);
|
||||
|
||||
/// <summary>
|
||||
/// This function creates a thread which is another function in your program previously declared using the PI_THREAD declaration.
|
||||
/// This function is then run concurrently with your main program. An example may be to have this function wait for an interrupt while
|
||||
/// your program carries on doing other tasks. The thread can indicate an event, or action by using global variables to
|
||||
/// communicate back to the main program, or other threads.
|
||||
/// </summary>
|
||||
/// <param name="method">The method.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piThreadCreate", SetLastError = true)]
|
||||
public static extern int PiThreadCreate(ThreadWorker method);
|
||||
|
||||
/// <summary>
|
||||
/// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key.
|
||||
/// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key.
|
||||
/// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread
|
||||
/// – otherwise it’s possible that the thread could wake-up halfway during your data copy and change the data –
|
||||
/// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piLock", SetLastError = true)]
|
||||
public static extern void PiLock(int key);
|
||||
|
||||
/// <summary>
|
||||
/// These allow you to synchronise variable updates from your main program to any threads running in your program. keyNum is a number from 0 to 3 and represents a key.
|
||||
/// When another process tries to lock the same key, it will be stalled until the first process has unlocked the same key.
|
||||
/// You may need to use these functions to ensure that you get valid data when exchanging data between your main program and a thread
|
||||
/// – otherwise it’s possible that the thread could wake-up halfway during your data copy and change the data –
|
||||
/// so the data you end up copying is incomplete, or invalid. See the wfi.c program in the examples directory for an example.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piUnlock", SetLastError = true)]
|
||||
public static extern void PiUnlock(int key);
|
||||
|
||||
/// <summary>
|
||||
/// This attempts to shift your program (or thread in a multi-threaded program) to a higher priority
|
||||
/// and enables a real-time scheduling. The priority parameter should be from 0 (the default) to 99 (the maximum).
|
||||
/// This won’t make your program go any faster, but it will give it a bigger slice of time when other programs are running.
|
||||
/// The priority parameter works relative to others – so you can make one program priority 1 and another priority 2
|
||||
/// and it will have the same effect as setting one to 10 and the other to 90 (as long as no other
|
||||
/// programs are running with elevated priorities)
|
||||
/// The return value is 0 for success and -1 for error. If an error is returned, the program should then consult the errno global variable, as per the usual conventions.
|
||||
/// Note: Only programs running as root can change their priority. If called from a non-root program then nothing happens.
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority.</param>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "piHiPri", SetLastError = true)]
|
||||
public static extern int PiHiPri(int priority);
|
||||
|
||||
/// <summary>
|
||||
/// This causes program execution to pause for at least howLong milliseconds.
|
||||
/// Due to the multi-tasking nature of Linux it could be longer.
|
||||
/// Note that the maximum delay is an unsigned 32-bit integer or approximately 49 days.
|
||||
/// </summary>
|
||||
/// <param name="howLong">The how long.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "delay", SetLastError = true)]
|
||||
public static extern void Delay(uint howLong);
|
||||
|
||||
/// <summary>
|
||||
/// This causes program execution to pause for at least howLong microseconds.
|
||||
/// Due to the multi-tasking nature of Linux it could be longer.
|
||||
/// Note that the maximum delay is an unsigned 32-bit integer microseconds or approximately 71 minutes.
|
||||
/// Delays under 100 microseconds are timed using a hard-coded loop continually polling the system time,
|
||||
/// Delays over 100 microseconds are done using the system nanosleep() function – You may need to consider the implications
|
||||
/// of very short delays on the overall performance of the system, especially if using threads.
|
||||
/// </summary>
|
||||
/// <param name="howLong">The how long.</param>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "delayMicroseconds", SetLastError = true)]
|
||||
public static extern void DelayMicroseconds(uint howLong);
|
||||
|
||||
/// <summary>
|
||||
/// This returns a number representing the number of milliseconds since your program called one of the wiringPiSetup functions.
|
||||
/// It returns an unsigned 32-bit number which wraps after 49 days.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "millis", SetLastError = true)]
|
||||
public static extern uint Millis();
|
||||
|
||||
/// <summary>
|
||||
/// This returns a number representing the number of microseconds since your program called one of
|
||||
/// the wiringPiSetup functions. It returns an unsigned 32-bit number which wraps after approximately 71 minutes.
|
||||
/// </summary>
|
||||
/// <returns>The result code</returns>
|
||||
[DllImport(WiringPiLibrary, EntryPoint = "micros", SetLastError = true)]
|
||||
public static extern uint Micros();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
110
Unosquare.RaspberryIO/Pi.cs
Normal file
110
Unosquare.RaspberryIO/Pi.cs
Normal file
@ -0,0 +1,110 @@
|
||||
namespace Unosquare.RaspberryIO
|
||||
{
|
||||
using Camera;
|
||||
using Computer;
|
||||
using Gpio;
|
||||
using Native;
|
||||
using System.Threading.Tasks;
|
||||
using Swan.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Our main character. Provides access to the Raspberry Pi's GPIO, system and board information and Camera
|
||||
/// </summary>
|
||||
public static class Pi
|
||||
{
|
||||
private static readonly object SyncLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="Pi" /> class.
|
||||
/// </summary>
|
||||
static Pi()
|
||||
{
|
||||
lock (SyncLock)
|
||||
{
|
||||
// Extraction of embedded resources
|
||||
Resources.EmbeddedResources.ExtractAll();
|
||||
|
||||
// Instance assignments
|
||||
Gpio = GpioController.Instance;
|
||||
Info = SystemInfo.Instance;
|
||||
Timing = Timing.Instance;
|
||||
Spi = SpiBus.Instance;
|
||||
I2C = I2CBus.Instance;
|
||||
Camera = CameraController.Instance;
|
||||
PiDisplay = DsiDisplay.Instance;
|
||||
}
|
||||
}
|
||||
|
||||
#region Components
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the Raspberry Pi's GPIO as a collection of GPIO Pins.
|
||||
/// </summary>
|
||||
public static GpioController Gpio { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides information on this Raspberry Pi's CPU and form factor.
|
||||
/// </summary>
|
||||
public static SystemInfo Info { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to The PI's Timing and threading API
|
||||
/// </summary>
|
||||
public static Timing Timing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the 2-channel SPI bus
|
||||
/// </summary>
|
||||
public static SpiBus Spi { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the functionality of the i2c bus.
|
||||
/// </summary>
|
||||
public static I2CBus I2C { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the official Raspberry Pi Camera
|
||||
/// </summary>
|
||||
public static CameraController Camera { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to the official Raspberry Pi 7-inch DSI Display
|
||||
/// </summary>
|
||||
public static DsiDisplay PiDisplay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the logger source name.
|
||||
/// </summary>
|
||||
internal static string LoggerSource => typeof(Pi).Namespace;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Methods
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the Pi. Must be running as SU
|
||||
/// </summary>
|
||||
/// <returns>The process result</returns>
|
||||
public static async Task<ProcessResult> RestartAsync() => await ProcessRunner.GetProcessResultAsync("reboot", null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Restarts the Pi. Must be running as SU
|
||||
/// </summary>
|
||||
/// <returns>The process result</returns>
|
||||
public static ProcessResult Restart() => RestartAsync().GetAwaiter().GetResult();
|
||||
|
||||
/// <summary>
|
||||
/// Halts the Pi. Must be running as SU
|
||||
/// </summary>
|
||||
/// <returns>The process result</returns>
|
||||
public static async Task<ProcessResult> ShutdownAsync() => await ProcessRunner.GetProcessResultAsync("halt", null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Halts the Pi. Must be running as SU
|
||||
/// </summary>
|
||||
/// <returns>The process result</returns>
|
||||
public static ProcessResult Shutdown() => ShutdownAsync().GetAwaiter().GetResult();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
36
Unosquare.RaspberryIO/Properties/AssemblyInfo.cs
Normal file
36
Unosquare.RaspberryIO/Properties/AssemblyInfo.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// Allgemeine Informationen über eine Assembly werden über die folgenden
|
||||
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
|
||||
// die einer Assembly zugeordnet sind.
|
||||
[assembly: AssemblyTitle("Unosquare Raspberry IO")]
|
||||
[assembly: AssemblyDescription("The Raspberry Pi's IO Functionality in an easy-to use API for Mono/.NET Core\n\nThis library enables developers to use the various Raspberry Pi's hardware modules including the Camera to capture images and video, the GPIO pins, and both, the SPI and I2C buses.")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Unosquare")]
|
||||
[assembly: AssemblyProduct("Unosquare.RaspberryIO")]
|
||||
[assembly: AssemblyCopyright("Unosquare (c) 2016-2018")]
|
||||
[assembly: AssemblyTrademark("https://github.com/unosquare/raspberryio")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Durch Festlegen von ComVisible auf FALSE werden die Typen in dieser Assembly
|
||||
// für COM-Komponenten unsichtbar. Wenn Sie auf einen Typ in dieser Assembly von
|
||||
// COM aus zugreifen müssen, sollten Sie das ComVisible-Attribut für diesen Typ auf "True" festlegen.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
|
||||
[assembly: Guid("8c5d4de9-377f-4ec8-873d-6eef15f43516")]
|
||||
|
||||
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
|
||||
//
|
||||
// Hauptversion
|
||||
// Nebenversion
|
||||
// Buildnummer
|
||||
// Revision
|
||||
//
|
||||
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
|
||||
// indem Sie "*" wie unten gezeigt eingeben:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.17.0")]
|
||||
[assembly: AssemblyFileVersion("0.17.0")]
|
65
Unosquare.RaspberryIO/Resources/EmbeddedResources.cs
Normal file
65
Unosquare.RaspberryIO/Resources/EmbeddedResources.cs
Normal file
@ -0,0 +1,65 @@
|
||||
namespace Unosquare.RaspberryIO.Resources
|
||||
{
|
||||
using Native;
|
||||
using Swan;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to embedded assembly files
|
||||
/// </summary>
|
||||
internal static class EmbeddedResources
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes static members of the <see cref="EmbeddedResources"/> class.
|
||||
/// </summary>
|
||||
static EmbeddedResources()
|
||||
{
|
||||
ResourceNames =
|
||||
new ReadOnlyCollection<string>(typeof(EmbeddedResources).Assembly().GetManifestResourceNames());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resource names.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The resource names.
|
||||
/// </value>
|
||||
public static ReadOnlyCollection<string> ResourceNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all the file resources to the specified base path.
|
||||
/// </summary>
|
||||
public static void ExtractAll()
|
||||
{
|
||||
var basePath = Runtime.EntryAssemblyDirectory;
|
||||
var executablePermissions = Standard.StringToInteger("0777", IntPtr.Zero, 8);
|
||||
|
||||
foreach (var resourceName in ResourceNames)
|
||||
{
|
||||
var filename = resourceName.Substring($"{typeof(EmbeddedResources).Namespace}.".Length);
|
||||
var targetPath = Path.Combine(basePath, filename);
|
||||
if (File.Exists(targetPath)) return;
|
||||
|
||||
using (var stream = typeof(EmbeddedResources).Assembly()
|
||||
.GetManifestResourceStream($"{typeof(EmbeddedResources).Namespace}.{filename}"))
|
||||
{
|
||||
using (var outputStream = File.OpenWrite(targetPath))
|
||||
{
|
||||
stream?.CopyTo(outputStream);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Standard.Chmod(targetPath, (uint)executablePermissions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* Ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
BIN
Unosquare.RaspberryIO/Resources/gpio.2.44
Normal file
BIN
Unosquare.RaspberryIO/Resources/gpio.2.44
Normal file
Binary file not shown.
BIN
Unosquare.RaspberryIO/Resources/libwiringPi.so.2.46
Normal file
BIN
Unosquare.RaspberryIO/Resources/libwiringPi.so.2.46
Normal file
Binary file not shown.
99
Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj
Normal file
99
Unosquare.RaspberryIO/Unosquare.RaspberryIO.csproj
Normal file
@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{8C5D4DE9-377F-4EC8-873D-6EEF15F43516}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Unosquare.RaspberryIO</RootNamespace>
|
||||
<AssemblyName>Unosquare.RaspberryIO</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;NET452</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;NET452</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Camera\CameraColor.cs" />
|
||||
<Compile Include="Camera\CameraController.cs" />
|
||||
<Compile Include="Camera\CameraRect.cs" />
|
||||
<Compile Include="Camera\CameraSettingsBase.cs" />
|
||||
<Compile Include="Camera\CameraStillSettings.cs" />
|
||||
<Compile Include="Camera\CameraVideoSettings.cs" />
|
||||
<Compile Include="Camera\Enums.cs" />
|
||||
<Compile Include="Computer\DsiDisplay.cs" />
|
||||
<Compile Include="Computer\NetworkAdapterInfo.cs" />
|
||||
<Compile Include="Computer\NetworkSettings.cs" />
|
||||
<Compile Include="Computer\OsInfo.cs" />
|
||||
<Compile Include="Computer\PiVersion.cs" />
|
||||
<Compile Include="Computer\SystemInfo.cs" />
|
||||
<Compile Include="Computer\WirelessNetworkInfo.cs" />
|
||||
<Compile Include="Gpio\Enums.cs" />
|
||||
<Compile Include="Gpio\GpioController.cs" />
|
||||
<Compile Include="Gpio\GpioPin.cs" />
|
||||
<Compile Include="Gpio\GpioPin.Factory.cs" />
|
||||
<Compile Include="Gpio\I2CBus.cs" />
|
||||
<Compile Include="Gpio\I2CDevice.cs" />
|
||||
<Compile Include="Gpio\SpiBus.cs" />
|
||||
<Compile Include="Gpio\SpiChannel.cs" />
|
||||
<Compile Include="Native\Delegates.cs" />
|
||||
<Compile Include="Native\HardwareException.cs" />
|
||||
<Compile Include="Native\HighResolutionTimer.cs" />
|
||||
<Compile Include="Native\Standard.cs" />
|
||||
<Compile Include="Native\SystemName.cs" />
|
||||
<Compile Include="Native\ThreadLockKey.cs" />
|
||||
<Compile Include="Native\Timing.cs" />
|
||||
<Compile Include="Native\WiringPi.cs" />
|
||||
<Compile Include="Native\WiringPi.I2C.cs" />
|
||||
<Compile Include="Native\WiringPi.SerialPort.cs" />
|
||||
<Compile Include="Native\WiringPi.Shift.cs" />
|
||||
<Compile Include="Native\WiringPi.SoftPwm.cs" />
|
||||
<Compile Include="Native\WiringPi.Spi.cs" />
|
||||
<Compile Include="Pi.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Resources\EmbeddedResources.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\gpio.2.44" />
|
||||
<EmbeddedResource Include="Resources\libwiringPi.so.2.46" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Unosquare.Swan.Lite\Unosquare.Swan.Lite.csproj">
|
||||
<Project>{ab015683-62e5-47f1-861f-6d037f9c6433}</Project>
|
||||
<Name>Unosquare.Swan.Lite</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Unosquare.Swan\Unosquare.Swan.csproj">
|
||||
<Project>{2ea5e3e4-f8c8-4742-8c78-4b070afcfb73}</Project>
|
||||
<Name>Unosquare.Swan</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
243
Unosquare.Swan.Lite/Abstractions/AtomicTypeBase.cs
Normal file
243
Unosquare.Swan.Lite/Abstractions/AtomicTypeBase.cs
Normal file
@ -0,0 +1,243 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a generic implementation of an Atomic (interlocked) type
|
||||
///
|
||||
/// Idea taken from Memory model and .NET operations in article:
|
||||
/// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The structure type backed by a 64-bit value.</typeparam>
|
||||
public abstract class AtomicTypeBase<T> : IComparable, IComparable<T>, IComparable<AtomicTypeBase<T>>, IEquatable<T>, IEquatable<AtomicTypeBase<T>>
|
||||
where T : struct, IComparable, IComparable<T>, IEquatable<T>
|
||||
{
|
||||
private long _backingValue;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicTypeBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">The initial value.</param>
|
||||
protected AtomicTypeBase(long initialValue)
|
||||
{
|
||||
BackingValue = initialValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value.
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => FromLong(BackingValue);
|
||||
set => BackingValue = ToLong(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the backing value.
|
||||
/// </summary>
|
||||
protected long BackingValue
|
||||
{
|
||||
get => Interlocked.Read(ref _backingValue);
|
||||
set => Interlocked.Exchange(ref _backingValue, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ==.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator ==(AtomicTypeBase<T> a, T b) => a?.Equals(b) == true;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator !=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator !=(AtomicTypeBase<T> a, T b) => a?.Equals(b) == false;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >(AtomicTypeBase<T> a, T b) => a.CompareTo(b) > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <(AtomicTypeBase<T> a, T b) => a.CompareTo(b) < 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator >=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator >=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator <=.
|
||||
/// </summary>
|
||||
/// <param name="a">a.</param>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static bool operator <=(AtomicTypeBase<T> a, T b) => a.CompareTo(b) <= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator ++.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator ++(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Increment(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator --.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator --(AtomicTypeBase<T> instance)
|
||||
{
|
||||
Interlocked.Decrement(ref instance._backingValue);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -<.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator +(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue + operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the operator -.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <param name="operand">The operand.</param>
|
||||
/// <returns>
|
||||
/// The result of the operator.
|
||||
/// </returns>
|
||||
public static AtomicTypeBase<T> operator -(AtomicTypeBase<T> instance, long operand)
|
||||
{
|
||||
instance.BackingValue = instance.BackingValue - operand;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
/// <exception cref="ArgumentException">When types are incompatible.</exception>
|
||||
public int CompareTo(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case null:
|
||||
return 1;
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return BackingValue.CompareTo(atomic.BackingValue);
|
||||
case T variable:
|
||||
return Value.CompareTo(variable);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Incompatible comparison types");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(T other) => Value.CompareTo(other);
|
||||
|
||||
/// <summary>
|
||||
/// Compares the value to the other instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The other instance.</param>
|
||||
/// <returns>0 if equal, 1 if this instance is greater, -1 if this instance is less than.</returns>
|
||||
public int CompareTo(AtomicTypeBase<T> other) => BackingValue.CompareTo(other?.BackingValue ?? default);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="object" />, is equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="other">The <see cref="object" /> to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified <see cref="object" /> is equal to this instance; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public override bool Equals(object other)
|
||||
{
|
||||
switch (other)
|
||||
{
|
||||
case AtomicTypeBase<T> atomic:
|
||||
return Equals(atomic);
|
||||
case T variable:
|
||||
return Equals(variable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode() => BackingValue.GetHashCode();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(AtomicTypeBase<T> other) =>
|
||||
BackingValue == (other?.BackingValue ?? default);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(T other) => Equals(Value, other);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from a long value to the target type.
|
||||
/// </summary>
|
||||
/// <param name="backingValue">The backing value.</param>
|
||||
/// <returns>The value converted form a long value.</returns>
|
||||
protected abstract T FromLong(long backingValue);
|
||||
|
||||
/// <summary>
|
||||
/// Converts from the target type to a long value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The value converted to a long value.</returns>
|
||||
protected abstract long ToLong(T value);
|
||||
}
|
||||
}
|
197
Unosquare.Swan.Lite/Abstractions/ExclusiveTimer.cs
Normal file
197
Unosquare.Swan.Lite/Abstractions/ExclusiveTimer.cs
Normal file
@ -0,0 +1,197 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
/// <summary>
|
||||
/// A threading <see cref="_backingTimer"/> implementation that executes at most one cycle at a time
|
||||
/// in a <see cref="ThreadPool"/> thread. Callback execution is NOT guaranteed to be carried out
|
||||
/// on the same <see cref="ThreadPool"/> thread every time the timer fires.
|
||||
/// </summary>
|
||||
public sealed class ExclusiveTimer : IDisposable
|
||||
{
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly ManualResetEventSlim _cycleDoneEvent = new ManualResetEventSlim(true);
|
||||
private readonly Timer _backingTimer;
|
||||
private readonly TimerCallback _userCallback;
|
||||
private readonly AtomicBoolean _isDisposing = new AtomicBoolean();
|
||||
private readonly AtomicBoolean _isDisposed = new AtomicBoolean();
|
||||
private int _period;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
_userCallback = timerCallback;
|
||||
_backingTimer = new Timer(InternalCallback, state ?? this, dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="state">The state.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback, object state, TimeSpan dueTime, TimeSpan period)
|
||||
: this(timerCallback, state, Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(TimerCallback timerCallback)
|
||||
: this(timerCallback, null, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, int dueTime, int period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public ExclusiveTimer(Action timerCallback, TimeSpan dueTime, TimeSpan period)
|
||||
: this(s => { timerCallback?.Invoke(); }, null, dueTime, period)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExclusiveTimer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="timerCallback">The timer callback.</param>
|
||||
public ExclusiveTimer(Action timerCallback)
|
||||
: this(timerCallback, Timeout.Infinite, Timeout.Infinite)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposing.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposing; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposing => _isDisposing.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is disposed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsDisposed => _isDisposed.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(int dueTime, int period)
|
||||
{
|
||||
_period = period;
|
||||
|
||||
_backingTimer.Change(dueTime, Timeout.Infinite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the start time and the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="dueTime">The due time.</param>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Change(TimeSpan dueTime, TimeSpan period)
|
||||
=> Change(Convert.ToInt32(dueTime.TotalMilliseconds), Convert.ToInt32(period.TotalMilliseconds));
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(int period) => Change(0, period);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the interval between method invocations for the internal timer.
|
||||
/// </summary>
|
||||
/// <param name="period">The period.</param>
|
||||
public void Resume(TimeSpan period) => Change(TimeSpan.Zero, period);
|
||||
|
||||
/// <summary>
|
||||
/// Pauses this instance.
|
||||
/// </summary>
|
||||
public void Pause() => Change(Timeout.Infinite, Timeout.Infinite);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_isDisposed == true || _isDisposing == true)
|
||||
return;
|
||||
|
||||
_isDisposing.Value = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_backingTimer.Dispose();
|
||||
_cycleDoneEvent.Wait();
|
||||
_cycleDoneEvent.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isDisposed.Value = true;
|
||||
_isDisposing.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logic that runs every time the timer hits the due time.
|
||||
/// </summary>
|
||||
/// <param name="state">The state.</param>
|
||||
private void InternalCallback(object state)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (IsDisposed || IsDisposing)
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cycleDoneEvent.IsSet == false)
|
||||
return;
|
||||
|
||||
_cycleDoneEvent.Reset();
|
||||
|
||||
try
|
||||
{
|
||||
_userCallback(state);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cycleDoneEvent?.Set();
|
||||
_backingTimer?.Change(_period, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
94
Unosquare.Swan.Lite/Abstractions/ExpressionParser.cs
Normal file
94
Unosquare.Swan.Lite/Abstractions/ExpressionParser.cs
Normal file
@ -0,0 +1,94 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic expression parser.
|
||||
/// </summary>
|
||||
public abstract class ExpressionParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolves the expression.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of expression result.</typeparam>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The representation of the expression parsed.</returns>
|
||||
public virtual T ResolveExpression<T>(IEnumerable<Token> tokens)
|
||||
{
|
||||
var conversion = Expression.Convert(Parse(tokens), typeof(T));
|
||||
return Expression.Lambda<Func<T>>(conversion).Compile()();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified tokens.
|
||||
/// </summary>
|
||||
/// <param name="tokens">The tokens.</param>
|
||||
/// <returns>The final expression.</returns>
|
||||
public virtual Expression Parse(IEnumerable<Token> tokens)
|
||||
{
|
||||
var expressionStack = new List<Stack<Expression>>();
|
||||
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
if (expressionStack.Any() == false)
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
|
||||
switch (token.Type)
|
||||
{
|
||||
case TokenType.Wall:
|
||||
expressionStack.Add(new Stack<Expression>());
|
||||
break;
|
||||
case TokenType.Number:
|
||||
expressionStack.Last().Push(Expression.Constant(Convert.ToDecimal(token.Value)));
|
||||
break;
|
||||
case TokenType.Variable:
|
||||
ResolveVariable(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.String:
|
||||
expressionStack.Last().Push(Expression.Constant(token.Value));
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
ResolveOperator(token.Value, expressionStack.Last());
|
||||
break;
|
||||
case TokenType.Function:
|
||||
ResolveFunction(token.Value, expressionStack.Last());
|
||||
|
||||
if (expressionStack.Count > 1 && expressionStack.Last().Count == 1)
|
||||
{
|
||||
var lastValue = expressionStack.Last().Pop();
|
||||
expressionStack.Remove(expressionStack.Last());
|
||||
expressionStack.Last().Push(lastValue);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return expressionStack.Last().Pop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the variable.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveVariable(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the operator.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveOperator(string value, Stack<Expression> expressionStack);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the function.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="expressionStack">The expression stack.</param>
|
||||
public abstract void ResolveFunction(string value, Stack<Expression> expressionStack);
|
||||
}
|
||||
}
|
27
Unosquare.Swan.Lite/Abstractions/IObjectMap.cs
Normal file
27
Unosquare.Swan.Lite/Abstractions/IObjectMap.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Interface object map.
|
||||
/// </summary>
|
||||
public interface IObjectMap
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the map.
|
||||
/// </summary>
|
||||
Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
Type SourceType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the destination.
|
||||
/// </summary>
|
||||
Type DestinationType { get; }
|
||||
}
|
||||
}
|
24
Unosquare.Swan.Lite/Abstractions/ISyncLocker.cs
Normal file
24
Unosquare.Swan.Lite/Abstractions/ISyncLocker.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a generic interface for synchronized locking mechanisms.
|
||||
/// </summary>
|
||||
public interface ISyncLocker : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires a writer lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a reader lock.
|
||||
/// The lock is released when the returned locking object is disposed.
|
||||
/// </summary>
|
||||
/// <returns>A disposable locking object.</returns>
|
||||
IDisposable AcquireReaderLock();
|
||||
}
|
||||
}
|
21
Unosquare.Swan.Lite/Abstractions/IValidator.cs
Normal file
21
Unosquare.Swan.Lite/Abstractions/IValidator.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple Validator interface.
|
||||
/// </summary>
|
||||
public interface IValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The error message.
|
||||
/// </summary>
|
||||
string ErrorMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a value is valid.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="value"> The value.</param>
|
||||
/// <returns>True if it is valid.False if it is not.</returns>
|
||||
bool IsValid<T>(T value);
|
||||
}
|
||||
}
|
57
Unosquare.Swan.Lite/Abstractions/IWaitEvent.cs
Normal file
57
Unosquare.Swan.Lite/Abstractions/IWaitEvent.cs
Normal file
@ -0,0 +1,57 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a generalized API for ManualResetEvent and ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
public interface IWaitEvent : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event is in the completed state.
|
||||
/// </summary>
|
||||
bool IsCompleted { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the Begin method has been called.
|
||||
/// It returns false after the Complete method is called.
|
||||
/// </summary>
|
||||
bool IsInProgress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the underlying handle is not closed and it is still valid.
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is disposed.
|
||||
/// </summary>
|
||||
bool IsDisposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Enters the state in which waiters need to wait.
|
||||
/// All future waiters will block when they call the Wait method.
|
||||
/// </summary>
|
||||
void Begin();
|
||||
|
||||
/// <summary>
|
||||
/// Leaves the state in which waiters need to wait.
|
||||
/// All current waiters will continue.
|
||||
/// </summary>
|
||||
void Complete();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// </summary>
|
||||
void Wait();
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the event to be completed.
|
||||
/// Returns <c>true</c> when there was no timeout. False if the timeout was reached.
|
||||
/// </summary>
|
||||
/// <param name="timeout">The maximum amount of time to wait for.</param>
|
||||
/// <returns><c>true</c> when there was no timeout. <c>false</c> if the timeout was reached.</returns>
|
||||
bool Wait(TimeSpan timeout);
|
||||
}
|
||||
}
|
18
Unosquare.Swan.Lite/Abstractions/IWorker.cs
Normal file
18
Unosquare.Swan.Lite/Abstractions/IWorker.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple interface for application workers.
|
||||
/// </summary>
|
||||
public interface IWorker
|
||||
{
|
||||
/// <summary>
|
||||
/// Should start the task immediately and asynchronously.
|
||||
/// </summary>
|
||||
void Start();
|
||||
|
||||
/// <summary>
|
||||
/// Should stop the task immediately and synchronously.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
}
|
||||
}
|
169
Unosquare.Swan.Lite/Abstractions/RunnerBase.cs
Normal file
169
Unosquare.Swan.Lite/Abstractions/RunnerBase.cs
Normal file
@ -0,0 +1,169 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Swan;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an background worker abstraction with a life cycle and running at a independent thread.
|
||||
/// </summary>
|
||||
public abstract class RunnerBase
|
||||
{
|
||||
private Thread _worker;
|
||||
private CancellationTokenSource _cancelTokenSource;
|
||||
private ManualResetEvent _workFinished;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RunnerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">if set to <c>true</c> [is enabled].</param>
|
||||
protected RunnerBase(bool isEnabled)
|
||||
{
|
||||
Name = GetType().Name;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error messages.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The error messages.
|
||||
/// </value>
|
||||
public List<string> ErrorMessages { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is running.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is running; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsRunning { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is enabled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if this instance is enabled; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Starts this instance.
|
||||
/// </summary>
|
||||
public virtual void Start()
|
||||
{
|
||||
if (IsEnabled == false)
|
||||
return;
|
||||
|
||||
$"Start Requested".Debug(Name);
|
||||
_cancelTokenSource = new CancellationTokenSource();
|
||||
_workFinished = new ManualResetEvent(false);
|
||||
|
||||
_worker = new Thread(() =>
|
||||
{
|
||||
_workFinished.Reset();
|
||||
IsRunning = true;
|
||||
try
|
||||
{
|
||||
Setup();
|
||||
DoBackgroundWork(_cancelTokenSource.Token);
|
||||
}
|
||||
catch (ThreadAbortException)
|
||||
{
|
||||
$"{nameof(ThreadAbortException)} caught.".Warn(Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
$"{ex.GetType()}: {ex.Message}\r\n{ex.StackTrace}".Error(Name);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Cleanup();
|
||||
_workFinished?.Set();
|
||||
IsRunning = false;
|
||||
"Stopped Completely".Debug(Name);
|
||||
}
|
||||
})
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = $"{Name}Thread",
|
||||
};
|
||||
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops this instance.
|
||||
/// </summary>
|
||||
public virtual void Stop()
|
||||
{
|
||||
if (IsEnabled == false || IsRunning == false)
|
||||
return;
|
||||
|
||||
$"Stop Requested".Debug(Name);
|
||||
_cancelTokenSource.Cancel();
|
||||
var waitRetries = 5;
|
||||
while (waitRetries >= 1)
|
||||
{
|
||||
if (_workFinished.WaitOne(250))
|
||||
{
|
||||
waitRetries = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
waitRetries--;
|
||||
}
|
||||
|
||||
if (waitRetries < 0)
|
||||
{
|
||||
"Workbench stopped gracefully".Debug(Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
"Did not respond to stop request. Aborting thread and waiting . . .".Warn(Name);
|
||||
_worker.Abort();
|
||||
|
||||
if (_workFinished.WaitOne(5000) == false)
|
||||
"Waited and no response. Worker might have been left in an inconsistent state.".Error(Name);
|
||||
else
|
||||
"Waited for worker and it finally responded (OK).".Debug(Name);
|
||||
}
|
||||
|
||||
_workFinished.Dispose();
|
||||
_workFinished = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Setup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleanups this instance.
|
||||
/// </summary>
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the background work.
|
||||
/// </summary>
|
||||
/// <param name="ct">The ct.</param>
|
||||
protected abstract void DoBackgroundWork(CancellationToken ct);
|
||||
}
|
||||
}
|
||||
#endif
|
188
Unosquare.Swan.Lite/Abstractions/SettingsProvider.cs
Normal file
188
Unosquare.Swan.Lite/Abstractions/SettingsProvider.cs
Normal file
@ -0,0 +1,188 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using Formatters;
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a provider to save and load settings using a plain JSON file.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to save and load settings.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Abstractions;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // get user from settings
|
||||
/// var user = SettingsProvider<Settings>.Instance.Global.User;
|
||||
///
|
||||
/// // modify the port
|
||||
/// SettingsProvider<Settings>.Instance.Global.Port = 20;
|
||||
///
|
||||
/// // if we want these settings to persist
|
||||
/// SettingsProvider<Settings>.Instance.PersistGlobalSettings();
|
||||
/// }
|
||||
///
|
||||
/// public class Settings
|
||||
/// {
|
||||
/// public int Port { get; set; } = 9696;
|
||||
///
|
||||
/// public string User { get; set; } = "User";
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <typeparam name="T">The type of settings model.</typeparam>
|
||||
public sealed class SettingsProvider<T>
|
||||
: SingletonBase<SettingsProvider<T>>
|
||||
{
|
||||
private readonly object _syncRoot = new object();
|
||||
|
||||
private T _global;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the configuration file path. By default the entry assembly directory is used
|
||||
/// and the filename is 'appsettings.json'.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The configuration file path.
|
||||
/// </value>
|
||||
public string ConfigurationFilePath { get; set; } =
|
||||
#if NETSTANDARD1_3
|
||||
Path.Combine(Runtime.LocalStoragePath, "appsettings.json");
|
||||
#else
|
||||
Path.Combine(Runtime.EntryAssemblyDirectory, "appsettings.json");
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the global settings object.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The global settings object.
|
||||
/// </value>
|
||||
public T Global
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncRoot)
|
||||
{
|
||||
if (Equals(_global, default(T)))
|
||||
ReloadGlobalSettings();
|
||||
|
||||
return _global;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the global settings.
|
||||
/// </summary>
|
||||
public void ReloadGlobalSettings()
|
||||
{
|
||||
if (File.Exists(ConfigurationFilePath) == false || File.ReadAllText(ConfigurationFilePath).Length == 0)
|
||||
{
|
||||
ResetGlobalSettings();
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_syncRoot)
|
||||
_global = Json.Deserialize<T>(File.ReadAllText(ConfigurationFilePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Persists the global settings.
|
||||
/// </summary>
|
||||
public void PersistGlobalSettings() => File.WriteAllText(ConfigurationFilePath, Json.Serialize(Global, true));
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings from list.
|
||||
/// </summary>
|
||||
/// <param name="propertyList">The list.</param>
|
||||
/// <returns>
|
||||
/// A list of settings of type ref="ExtendedPropertyInfo".
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">propertyList.</exception>
|
||||
public List<string> RefreshFromList(List<ExtendedPropertyInfo<T>> propertyList)
|
||||
{
|
||||
if (propertyList == null)
|
||||
throw new ArgumentNullException(nameof(propertyList));
|
||||
|
||||
var changedSettings = new List<string>();
|
||||
var globalProps = Runtime.PropertyTypeCache.RetrieveAllProperties<T>();
|
||||
|
||||
foreach (var property in propertyList)
|
||||
{
|
||||
var propertyInfo = globalProps.FirstOrDefault(x => x.Name == property.Property);
|
||||
|
||||
if (propertyInfo == null) continue;
|
||||
|
||||
var originalValue = propertyInfo.GetValue(Global);
|
||||
var isChanged = propertyInfo.PropertyType.IsArray
|
||||
? property.Value is IEnumerable enumerable && propertyInfo.TrySetArray(enumerable.Cast<object>(), Global)
|
||||
: SetValue(property.Value, originalValue, propertyInfo);
|
||||
|
||||
if (!isChanged) continue;
|
||||
|
||||
changedSettings.Add(property.Property);
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
return changedSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list.
|
||||
/// </summary>
|
||||
/// <returns>A List of ExtendedPropertyInfo of the type T.</returns>
|
||||
public List<ExtendedPropertyInfo<T>> GetList()
|
||||
{
|
||||
var jsonData = Json.Deserialize(Json.Serialize(Global)) as Dictionary<string, object>;
|
||||
|
||||
return jsonData?.Keys
|
||||
.Select(p => new ExtendedPropertyInfo<T>(p) { Value = jsonData[p] })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the global settings.
|
||||
/// </summary>
|
||||
public void ResetGlobalSettings()
|
||||
{
|
||||
lock (_syncRoot)
|
||||
_global = Activator.CreateInstance<T>();
|
||||
|
||||
PersistGlobalSettings();
|
||||
}
|
||||
|
||||
private bool SetValue(object property, object originalValue, PropertyInfo propertyInfo)
|
||||
{
|
||||
switch (property)
|
||||
{
|
||||
case null when originalValue == null:
|
||||
break;
|
||||
case null:
|
||||
propertyInfo.SetValue(Global, null);
|
||||
return true;
|
||||
default:
|
||||
if (propertyInfo.PropertyType.TryParseBasicType(property, out var propertyValue) &&
|
||||
!propertyValue.Equals(originalValue))
|
||||
{
|
||||
propertyInfo.SetValue(Global, propertyValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
59
Unosquare.Swan.Lite/Abstractions/SingletonBase.cs
Normal file
59
Unosquare.Swan.Lite/Abstractions/SingletonBase.cs
Normal file
@ -0,0 +1,59 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a singleton pattern abstract class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of class.</typeparam>
|
||||
public abstract class SingletonBase<T> : IDisposable
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The static, singleton instance reference.
|
||||
/// </summary>
|
||||
protected static readonly Lazy<T> LazyInstance = new Lazy<T>(
|
||||
valueFactory: () => Activator.CreateInstance(typeof(T), true) as T,
|
||||
isThreadSafe: true);
|
||||
|
||||
private bool _isDisposing; // To detect redundant calls
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that this singleton represents.
|
||||
/// If the instance is null, it is constructed and assigned when this member is accessed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The instance.
|
||||
/// </value>
|
||||
public static T Instance => LazyInstance.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// Call the GC.SuppressFinalize if you override this method and use
|
||||
/// a non-default class finalizer (destructor).
|
||||
/// </summary>
|
||||
/// <param name="disposeManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposeManaged)
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
_isDisposing = true;
|
||||
|
||||
// free managed resources
|
||||
if (LazyInstance == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var disposableInstance = LazyInstance.Value as IDisposable;
|
||||
disposableInstance?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
459
Unosquare.Swan.Lite/Abstractions/Tokenizer.cs
Normal file
459
Unosquare.Swan.Lite/Abstractions/Tokenizer.cs
Normal file
@ -0,0 +1,459 @@
|
||||
namespace Unosquare.Swan.Abstractions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a generic tokenizer.
|
||||
/// </summary>
|
||||
public abstract class Tokenizer
|
||||
{
|
||||
private const char PeriodChar = '.';
|
||||
private const char CommaChar = ',';
|
||||
private const char StringQuotedChar = '"';
|
||||
private const char OpenFuncChar = '(';
|
||||
private const char CloseFuncChar = ')';
|
||||
private const char NegativeChar = '-';
|
||||
|
||||
private const string OpenFuncStr = "(";
|
||||
|
||||
private readonly List<Operator> _operators = new List<Operator>();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer"/> class.
|
||||
/// This constructor will use the following default operators:
|
||||
///
|
||||
/// <list type="table">
|
||||
/// <listheader>
|
||||
/// <term>Operator</term>
|
||||
/// <description>Precedence</description>
|
||||
/// </listheader>
|
||||
/// <item>
|
||||
/// <term>=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>!=</term>
|
||||
/// <description>1</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>></term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>>=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term><=</term>
|
||||
/// <description>2</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>+</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>&</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>-</term>
|
||||
/// <description>3</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>*</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>(backslash)</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>/</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// <item>
|
||||
/// <term>^</term>
|
||||
/// <description>4</description>
|
||||
/// </item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
protected Tokenizer(string input)
|
||||
{
|
||||
_operators.AddRange(GetDefaultOperators());
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Tokenizer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="operators">The operators to use.</param>
|
||||
protected Tokenizer(string input, IEnumerable<Operator> operators)
|
||||
{
|
||||
_operators.AddRange(operators);
|
||||
Tokenize(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tokens.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The tokens.
|
||||
/// </value>
|
||||
public List<Token> Tokens { get; } = new List<Token>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates the input and return the start index for tokenizer.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <returns><c>true</c> if the input is valid, otherwise <c>false</c>.</returns>
|
||||
public abstract bool ValidateInput(string input, out int startIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the type of the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <returns>The token type.</returns>
|
||||
public abstract TokenType ResolveFunctionOrMemberType(string input);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the function or member.
|
||||
/// </summary>
|
||||
/// <param name="input">The input.</param>
|
||||
/// <param name="position">The position.</param>
|
||||
/// <returns><c>true</c> if the input is a valid function or variable, otherwise <c>false</c>.</returns>
|
||||
public virtual bool EvaluateFunctionOrMember(string input, int position) => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default operators.
|
||||
/// </summary>
|
||||
/// <returns>An array with the operators to use for the tokenizer.</returns>
|
||||
public virtual Operator[] GetDefaultOperators() => new[]
|
||||
{
|
||||
new Operator {Name = "=", Precedence = 1},
|
||||
new Operator {Name = "!=", Precedence = 1},
|
||||
new Operator {Name = ">", Precedence = 2},
|
||||
new Operator {Name = "<", Precedence = 2},
|
||||
new Operator {Name = ">=", Precedence = 2},
|
||||
new Operator {Name = "<=", Precedence = 2},
|
||||
new Operator {Name = "+", Precedence = 3},
|
||||
new Operator {Name = "&", Precedence = 3},
|
||||
new Operator {Name = "-", Precedence = 3},
|
||||
new Operator {Name = "*", Precedence = 4},
|
||||
new Operator {Name = "/", Precedence = 4},
|
||||
new Operator {Name = "\\", Precedence = 4},
|
||||
new Operator {Name = "^", Precedence = 4},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Shunting the yard.
|
||||
/// </summary>
|
||||
/// <param name="includeFunctionStopper">if set to <c>true</c> [include function stopper] (Token type <c>Wall</c>).</param>
|
||||
/// <returns>
|
||||
/// Enumerable of the token in in.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Wrong token
|
||||
/// or
|
||||
/// Mismatched parenthesis.
|
||||
/// </exception>
|
||||
public virtual IEnumerable<Token> ShuntingYard(bool includeFunctionStopper = true)
|
||||
{
|
||||
var stack = new Stack<Token>();
|
||||
|
||||
foreach (var tok in Tokens)
|
||||
{
|
||||
switch (tok.Type)
|
||||
{
|
||||
case TokenType.Number:
|
||||
case TokenType.Variable:
|
||||
case TokenType.String:
|
||||
yield return tok;
|
||||
break;
|
||||
case TokenType.Function:
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Operator:
|
||||
while (stack.Any() && stack.Peek().Type == TokenType.Operator &&
|
||||
CompareOperators(tok.Value, stack.Peek().Value))
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Push(tok);
|
||||
break;
|
||||
case TokenType.Comma:
|
||||
while (stack.Any() && (stack.Peek().Type != TokenType.Comma &&
|
||||
stack.Peek().Type != TokenType.Parenthesis))
|
||||
yield return stack.Pop();
|
||||
|
||||
break;
|
||||
case TokenType.Parenthesis:
|
||||
if (tok.Value == OpenFuncStr)
|
||||
{
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
if (includeFunctionStopper)
|
||||
yield return new Token(TokenType.Wall, tok.Value);
|
||||
}
|
||||
|
||||
stack.Push(tok);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (stack.Peek().Value != OpenFuncStr)
|
||||
yield return stack.Pop();
|
||||
|
||||
stack.Pop();
|
||||
|
||||
if (stack.Any() && stack.Peek().Type == TokenType.Function)
|
||||
{
|
||||
yield return stack.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Wrong token");
|
||||
}
|
||||
}
|
||||
|
||||
while (stack.Any())
|
||||
{
|
||||
var tok = stack.Pop();
|
||||
if (tok.Type == TokenType.Parenthesis)
|
||||
throw new InvalidOperationException("Mismatched parenthesis");
|
||||
|
||||
yield return tok;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CompareOperators(Operator op1, Operator op2) => op1.RightAssociative
|
||||
? op1.Precedence < op2.Precedence
|
||||
: op1.Precedence <= op2.Precedence;
|
||||
|
||||
private void Tokenize(string input)
|
||||
{
|
||||
if (!ValidateInput(input, out var startIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = startIndex; i < input.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(input, i)) continue;
|
||||
|
||||
if (input[i] == CommaChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Comma, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == StringQuotedChar)
|
||||
{
|
||||
i = ExtractString(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsLetter(input, i) || EvaluateFunctionOrMember(input, i))
|
||||
{
|
||||
i = ExtractFunctionOrMember(input, i);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (char.IsNumber(input, i) || (
|
||||
input[i] == NegativeChar &&
|
||||
((Tokens.Any() && Tokens.Last().Type != TokenType.Number) || !Tokens.Any())))
|
||||
{
|
||||
i = ExtractNumber(input, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input[i] == OpenFuncChar ||
|
||||
input[i] == CloseFuncChar)
|
||||
{
|
||||
Tokens.Add(new Token(TokenType.Parenthesis, new string(new[] { input[i] })));
|
||||
continue;
|
||||
}
|
||||
|
||||
i = ExtractOperator(input, i);
|
||||
}
|
||||
}
|
||||
|
||||
private int ExtractData(
|
||||
string input,
|
||||
int i,
|
||||
Func<string, TokenType> tokenTypeEvaluation,
|
||||
Func<char, bool> evaluation,
|
||||
int right = 0,
|
||||
int left = -1)
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = i + right; j < input.Length; j++)
|
||||
{
|
||||
if (evaluation(input[j]))
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var value = input.SliceLength(i + right, charCount);
|
||||
Tokens.Add(new Token(tokenTypeEvaluation(value), value));
|
||||
|
||||
i += charCount + left;
|
||||
return i;
|
||||
}
|
||||
|
||||
private int ExtractOperator(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Operator, x => x == OpenFuncChar ||
|
||||
x == CommaChar ||
|
||||
x == PeriodChar ||
|
||||
x == StringQuotedChar ||
|
||||
char.IsWhiteSpace(x) ||
|
||||
char.IsNumber(x));
|
||||
|
||||
private int ExtractFunctionOrMember(string input, int i) =>
|
||||
ExtractData(input, i, ResolveFunctionOrMemberType, x => x == OpenFuncChar ||
|
||||
x == CloseFuncChar ||
|
||||
x == CommaChar ||
|
||||
char.IsWhiteSpace(x));
|
||||
|
||||
private int ExtractNumber(string input, int i) =>
|
||||
ExtractData(input, i, x => TokenType.Number,
|
||||
x => !char.IsNumber(x) && x != PeriodChar && x != NegativeChar);
|
||||
|
||||
private int ExtractString(string input, int i)
|
||||
{
|
||||
var length = ExtractData(input, i, x => TokenType.String, x => x == StringQuotedChar, 1, 1);
|
||||
|
||||
// open string, report issue
|
||||
if (length == input.Length && input[length - 1] != StringQuotedChar)
|
||||
throw new FormatException($"Parser error (Position {i}): Expected '\"' but got '{input[length - 1]}'.");
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private bool CompareOperators(string op1, string op2)
|
||||
=> CompareOperators(GetOperatorOrDefault(op1), GetOperatorOrDefault(op2));
|
||||
|
||||
private Operator GetOperatorOrDefault(string op)
|
||||
=> _operators.FirstOrDefault(x => x.Name == op) ?? new Operator { Name = op, Precedence = 0 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an operator with precedence.
|
||||
/// </summary>
|
||||
public class Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the precedence.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The precedence.
|
||||
/// </value>
|
||||
public int Precedence { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [right associative].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [right associative]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool RightAssociative { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Token structure.
|
||||
/// </summary>
|
||||
public struct Token
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Token"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Token(TokenType type, string value)
|
||||
{
|
||||
Type = type;
|
||||
Value = type == TokenType.Function || type == TokenType.Operator ? value.ToLowerInvariant() : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type.
|
||||
/// </value>
|
||||
public TokenType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The value.
|
||||
/// </value>
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enums the token types.
|
||||
/// </summary>
|
||||
public enum TokenType
|
||||
{
|
||||
/// <summary>
|
||||
/// The number
|
||||
/// </summary>
|
||||
Number,
|
||||
|
||||
/// <summary>
|
||||
/// The string
|
||||
/// </summary>
|
||||
String,
|
||||
|
||||
/// <summary>
|
||||
/// The variable
|
||||
/// </summary>
|
||||
Variable,
|
||||
|
||||
/// <summary>
|
||||
/// The function
|
||||
/// </summary>
|
||||
Function,
|
||||
|
||||
/// <summary>
|
||||
/// The parenthesis
|
||||
/// </summary>
|
||||
Parenthesis,
|
||||
|
||||
/// <summary>
|
||||
/// The operator
|
||||
/// </summary>
|
||||
Operator,
|
||||
|
||||
/// <summary>
|
||||
/// The comma
|
||||
/// </summary>
|
||||
Comma,
|
||||
|
||||
/// <summary>
|
||||
/// The wall, used to specified the end of argument list of the following function
|
||||
/// </summary>
|
||||
Wall,
|
||||
}
|
||||
}
|
127
Unosquare.Swan.Lite/Abstractions/ViewModelBase.cs
Normal file
127
Unosquare.Swan.Lite/Abstractions/ViewModelBase.cs
Normal file
@ -0,0 +1,127 @@
|
||||
namespace Unosquare.Swan.Lite.Abstractions
|
||||
{
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for implementing models that fire notifications when their properties change.
|
||||
/// This class is ideal for implementing MVVM driven UIs.
|
||||
/// </summary>
|
||||
/// <seealso cref="INotifyPropertyChanged" />
|
||||
public abstract class ViewModelBase : INotifyPropertyChanged
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, bool> QueuedNotifications = new ConcurrentDictionary<string, bool>();
|
||||
private readonly bool UseDeferredNotifications;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="useDeferredNotifications">Set to <c>true</c> to use deferred notifications in the background.</param>
|
||||
protected ViewModelBase(bool useDeferredNotifications)
|
||||
{
|
||||
UseDeferredNotifications = useDeferredNotifications;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ViewModelBase"/> class.
|
||||
/// </summary>
|
||||
protected ViewModelBase()
|
||||
: this(false)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when a property value changes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
/// <summary>Checks if a property already matches a desired value. Sets the property and
|
||||
/// notifies listeners only when necessary.</summary>
|
||||
/// <typeparam name="T">Type of the property.</typeparam>
|
||||
/// <param name="storage">Reference to a property with both getter and setter.</param>
|
||||
/// <param name="value">Desired value for the property.</param>
|
||||
/// <param name="propertyName">Name of the property used to notify listeners. This
|
||||
/// value is optional and can be provided automatically when invoked from compilers that
|
||||
/// support CallerMemberName.</param>
|
||||
/// <param name="notifyAlso">An rray of property names to notify in addition to notifying the changes on the current property name.</param>
|
||||
/// <returns>True if the value was changed, false if the existing value matched the
|
||||
/// desired value.</returns>
|
||||
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "", string[] notifyAlso = null)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(storage, value))
|
||||
return false;
|
||||
|
||||
storage = value;
|
||||
NotifyPropertyChanged(propertyName, notifyAlso);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">The property names.</param>
|
||||
protected void NotifyPropertyChanged(params string[] propertyNames) => NotifyPropertyChanged(null, propertyNames);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies one or more properties changed.
|
||||
/// </summary>
|
||||
/// <param name="mainProperty">The main property.</param>
|
||||
/// <param name="auxiliaryProperties">The auxiliary properties.</param>
|
||||
private void NotifyPropertyChanged(string mainProperty, string[] auxiliaryProperties)
|
||||
{
|
||||
// Queue property notification
|
||||
if (string.IsNullOrWhiteSpace(mainProperty) == false)
|
||||
QueuedNotifications[mainProperty] = true;
|
||||
|
||||
// Set the state for notification properties
|
||||
if (auxiliaryProperties != null)
|
||||
{
|
||||
foreach (var property in auxiliaryProperties)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(property) == false)
|
||||
QueuedNotifications[property] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on operation mode, either fire the notifications in the background
|
||||
// or fire them immediately
|
||||
if (UseDeferredNotifications)
|
||||
Task.Run(() => NotifyQueuedProperties());
|
||||
else
|
||||
NotifyQueuedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the queued properties and resets the property name to a non-queued stated.
|
||||
/// </summary>
|
||||
private void NotifyQueuedProperties()
|
||||
{
|
||||
// get a snapshot of property names.
|
||||
var propertyNames = QueuedNotifications.Keys.ToArray();
|
||||
|
||||
// Iterate through the properties
|
||||
foreach (var property in propertyNames)
|
||||
{
|
||||
// don't notify if we don't have a change
|
||||
if (!QueuedNotifications[property]) continue;
|
||||
|
||||
// notify and reset queued state to false
|
||||
try { OnPropertyChanged(property); }
|
||||
finally { QueuedNotifications[property] = false; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a property changes its backing value.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
private void OnPropertyChanged(string propertyName) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName ?? string.Empty));
|
||||
}
|
||||
}
|
26
Unosquare.Swan.Lite/AtomicBoolean.cs
Normal file
26
Unosquare.Swan.Lite/AtomicBoolean.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Fast, atomic boolean combining interlocked to write value and volatile to read values.
|
||||
/// </summary>
|
||||
public sealed class AtomicBoolean : AtomicTypeBase<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicBoolean"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
|
||||
public AtomicBoolean(bool initialValue = default)
|
||||
: base(initialValue ? 1 : 0)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool FromLong(long backingValue) => backingValue != 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override long ToLong(bool value) => value ? 1 : 0;
|
||||
}
|
||||
}
|
29
Unosquare.Swan.Lite/AtomicDouble.cs
Normal file
29
Unosquare.Swan.Lite/AtomicDouble.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Fast, atomic double combining interlocked to write value and volatile to read values.
|
||||
/// </summary>
|
||||
public sealed class AtomicDouble : AtomicTypeBase<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicDouble"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
|
||||
public AtomicDouble(double initialValue = default)
|
||||
: base(BitConverter.DoubleToInt64Bits(initialValue))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override double FromLong(long backingValue) =>
|
||||
BitConverter.Int64BitsToDouble(backingValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override long ToLong(double value) =>
|
||||
BitConverter.DoubleToInt64Bits(value);
|
||||
}
|
||||
}
|
29
Unosquare.Swan.Lite/AtomicInteger.cs
Normal file
29
Unosquare.Swan.Lite/AtomicInteger.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an atomically readable or writable integer.
|
||||
/// </summary>
|
||||
public class AtomicInteger : AtomicTypeBase<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicInteger"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
|
||||
public AtomicInteger(int initialValue = default)
|
||||
: base(Convert.ToInt64(initialValue))
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override int FromLong(long backingValue) =>
|
||||
Convert.ToInt32(backingValue);
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override long ToLong(int value) =>
|
||||
Convert.ToInt64(value);
|
||||
}
|
||||
}
|
26
Unosquare.Swan.Lite/AtomicLong.cs
Normal file
26
Unosquare.Swan.Lite/AtomicLong.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Fast, atomioc long combining interlocked to write value and volatile to read values.
|
||||
/// </summary>
|
||||
public sealed class AtomicLong : AtomicTypeBase<long>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AtomicLong"/> class.
|
||||
/// </summary>
|
||||
/// <param name="initialValue">if set to <c>true</c> [initial value].</param>
|
||||
public AtomicLong(long initialValue = default)
|
||||
: base(initialValue)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override long FromLong(long backingValue) => backingValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override long ToLong(long value) => value;
|
||||
}
|
||||
}
|
102
Unosquare.Swan.Lite/Attributes/ArgumentOptionAttribute.cs
Normal file
102
Unosquare.Swan.Lite/Attributes/ArgumentOptionAttribute.cs
Normal file
@ -0,0 +1,102 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Models an option specification.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class ArgumentOptionAttribute
|
||||
: Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
|
||||
/// The default long name will be inferred from target property.
|
||||
/// </summary>
|
||||
public ArgumentOptionAttribute()
|
||||
: this(string.Empty, string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="longName">The long name of the option.</param>
|
||||
public ArgumentOptionAttribute(string longName)
|
||||
: this(string.Empty, longName)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="shortName">The short name of the option.</param>
|
||||
/// <param name="longName">The long name of the option or null if not used.</param>
|
||||
public ArgumentOptionAttribute(char shortName, string longName)
|
||||
: this(new string(shortName, 1), longName)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentOptionAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="shortName">The short name of the option..</param>
|
||||
public ArgumentOptionAttribute(char shortName)
|
||||
: this(new string(shortName, 1), string.Empty)
|
||||
{
|
||||
}
|
||||
|
||||
private ArgumentOptionAttribute(string shortName, string longName)
|
||||
{
|
||||
ShortName = shortName ?? throw new ArgumentNullException(nameof(shortName));
|
||||
LongName = longName ?? throw new ArgumentNullException(nameof(longName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets long name of this command line option. This name is usually a single English word.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The long name.
|
||||
/// </value>
|
||||
public string LongName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a short name of this command line option, made of one character.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The short name.
|
||||
/// </value>
|
||||
public string ShortName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When applying attribute to <see cref="System.Collections.Generic.IEnumerable{T}"/> target properties,
|
||||
/// it allows you to split an argument and consume its content as a sequence.
|
||||
/// </summary>
|
||||
public char Separator { get; set; } = '\0';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets mapped property default value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default value.
|
||||
/// </value>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a command line option is required.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if required; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool Required { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short description of this command line option. Usually a sentence summary.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The help text.
|
||||
/// </value>
|
||||
public string HelpText { get; set; }
|
||||
}
|
||||
}
|
13
Unosquare.Swan.Lite/Attributes/CopyableAttribute.cs
Normal file
13
Unosquare.Swan.Lite/Attributes/CopyableAttribute.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an attribute to select which properties are copyable between objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CopyableAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
39
Unosquare.Swan.Lite/Attributes/JsonPropertyAttribute.cs
Normal file
39
Unosquare.Swan.Lite/Attributes/JsonPropertyAttribute.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute used to help setup a property behavior when serialize/deserialize JSON.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class JsonPropertyAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonPropertyAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">Name of the property.</param>
|
||||
/// <param name="ignored">if set to <c>true</c> [ignored].</param>
|
||||
public JsonPropertyAttribute(string propertyName, bool ignored = false)
|
||||
{
|
||||
PropertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName));
|
||||
Ignored = ignored;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the property.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the property.
|
||||
/// </value>
|
||||
public string PropertyName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this <see cref="JsonPropertyAttribute" /> is ignored.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if ignored; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool Ignored { get; }
|
||||
}
|
||||
}
|
54
Unosquare.Swan.Lite/Attributes/PropertyDisplayAttribute.cs
Normal file
54
Unosquare.Swan.Lite/Attributes/PropertyDisplayAttribute.cs
Normal file
@ -0,0 +1,54 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute used to include additional information to a Property for serialization.
|
||||
///
|
||||
/// Previously we used DisplayAttribute from DataAnnotation.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class PropertyDisplayAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name.
|
||||
/// </value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the description.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The description.
|
||||
/// </value>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the group.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the group.
|
||||
/// </value>
|
||||
public string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default value.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The default value.
|
||||
/// </value>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the format string to call with method <c>ToString</c>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The format.
|
||||
/// </value>
|
||||
public string Format { get; set; }
|
||||
}
|
||||
}
|
30
Unosquare.Swan.Lite/Attributes/StructEndiannessAttribute.cs
Normal file
30
Unosquare.Swan.Lite/Attributes/StructEndiannessAttribute.cs
Normal file
@ -0,0 +1,30 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// An attribute used to help conversion structs back and forth into arrays of bytes via
|
||||
/// extension methods included in this library ToStruct and ToBytes.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct)]
|
||||
public class StructEndiannessAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StructEndiannessAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="endianness">The endianness.</param>
|
||||
public StructEndiannessAttribute(Endianness endianness)
|
||||
{
|
||||
Endianness = endianness;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the endianness.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The endianness.
|
||||
/// </value>
|
||||
public Endianness Endianness { get; }
|
||||
}
|
||||
}
|
133
Unosquare.Swan.Lite/Attributes/Validators.cs
Normal file
133
Unosquare.Swan.Lite/Attributes/Validators.cs
Normal file
@ -0,0 +1,133 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Regex validator.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class MatchAttribute : Attribute, IValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MatchAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="regex">A regex string.</param>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
/// <exception cref="ArgumentNullException">Expression.</exception>
|
||||
public MatchAttribute(string regex, string errorMessage = null)
|
||||
{
|
||||
Expression = regex ?? throw new ArgumentNullException(nameof(Expression));
|
||||
ErrorMessage = errorMessage ?? "String does not match the specified regular expression";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The string regex used to find a match.
|
||||
/// </summary>
|
||||
public string Expression { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ErrorMessage { get; internal set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsValid<T>(T value)
|
||||
{
|
||||
if (Equals(value, default(T)))
|
||||
return false;
|
||||
|
||||
return !(value is string)
|
||||
? throw new ArgumentException("Property is not a string")
|
||||
: Regex.IsMatch(value.ToString(), Expression);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Email validator.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class EmailAttribute : MatchAttribute
|
||||
{
|
||||
private const string EmailRegExp =
|
||||
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
|
||||
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EmailAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
public EmailAttribute(string errorMessage = null)
|
||||
: base(EmailRegExp, errorMessage ?? "String is not an email")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A not null validator.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class NotNullAttribute : Attribute, IValidator
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public string ErrorMessage => "Value is null";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsValid<T>(T value) => !Equals(default(T), value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A range constraint validator.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class RangeAttribute : Attribute, IValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeAttribute"/> class.
|
||||
/// Constructor that takes integer minimum and maximum values.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value.</param>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
public RangeAttribute(int min, int max)
|
||||
{
|
||||
if (min >= max)
|
||||
throw new InvalidOperationException("Maximum value must be greater than minimum");
|
||||
|
||||
Maximum = max;
|
||||
Minimum = min;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RangeAttribute"/> class.
|
||||
/// Constructor that takes double minimum and maximum values.
|
||||
/// </summary>
|
||||
/// <param name="min">The minimum value.</param>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
public RangeAttribute(double min, double max)
|
||||
{
|
||||
if (min >= max)
|
||||
throw new InvalidOperationException("Maximum value must be greater than minimum");
|
||||
|
||||
Maximum = max;
|
||||
Minimum = min;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string ErrorMessage => "Value is not within the specified range";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum value for the range.
|
||||
/// </summary>
|
||||
public IComparable Maximum { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Minimum value for the range.
|
||||
/// </summary>
|
||||
public IComparable Minimum { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsValid<T>(T value)
|
||||
=> value is IComparable comparable
|
||||
? comparable.CompareTo(Minimum) >= 0 && comparable.CompareTo(Maximum) <= 0
|
||||
: throw new ArgumentException(nameof(value));
|
||||
}
|
||||
}
|
40
Unosquare.Swan.Lite/Attributes/VerbOptionAttribute.cs
Normal file
40
Unosquare.Swan.Lite/Attributes/VerbOptionAttribute.cs
Normal file
@ -0,0 +1,40 @@
|
||||
namespace Unosquare.Swan.Attributes
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Models a verb option.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class VerbOptionAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerbOptionAttribute" /> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <exception cref="ArgumentNullException">name.</exception>
|
||||
public VerbOptionAttribute(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the verb option.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// Name.
|
||||
/// </value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a short description of this command line verb. Usually a sentence summary.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The help text.
|
||||
/// </value>
|
||||
public string HelpText { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => $" {Name}\t\t{HelpText}";
|
||||
}
|
||||
}
|
159
Unosquare.Swan.Lite/Components/ArgumentParse.Validator.cs
Normal file
159
Unosquare.Swan.Lite/Components/ArgumentParse.Validator.cs
Normal file
@ -0,0 +1,159 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
public partial class ArgumentParser
|
||||
{
|
||||
private sealed class Validator
|
||||
{
|
||||
private readonly object _instance;
|
||||
private readonly IEnumerable<string> _args;
|
||||
private readonly List<PropertyInfo> _updatedList = new List<PropertyInfo>();
|
||||
private readonly ArgumentParserSettings _settings;
|
||||
|
||||
private readonly PropertyInfo[] _properties;
|
||||
|
||||
public Validator(
|
||||
PropertyInfo[] properties,
|
||||
IEnumerable<string> args,
|
||||
object instance,
|
||||
ArgumentParserSettings settings)
|
||||
{
|
||||
_args = args;
|
||||
_instance = instance;
|
||||
_settings = settings;
|
||||
_properties = properties;
|
||||
|
||||
PopulateInstance();
|
||||
SetDefaultValues();
|
||||
GetRequiredList();
|
||||
}
|
||||
|
||||
public List<string> UnknownList { get; } = new List<string>();
|
||||
public List<string> RequiredList { get; } = new List<string>();
|
||||
|
||||
public bool IsValid() => (_settings.IgnoreUnknownArguments || !UnknownList.Any()) && !RequiredList.Any();
|
||||
|
||||
public IEnumerable<ArgumentOptionAttribute> GetPropertiesOptions()
|
||||
=> _properties.Select(p => Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p))
|
||||
.Where(x => x != null);
|
||||
|
||||
private void GetRequiredList()
|
||||
{
|
||||
foreach (var targetProperty in _properties)
|
||||
{
|
||||
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
if (optionAttr == null || optionAttr.Required == false)
|
||||
continue;
|
||||
|
||||
if (targetProperty.GetValue(_instance) == null)
|
||||
{
|
||||
RequiredList.Add(optionAttr.LongName ?? optionAttr.ShortName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetDefaultValues()
|
||||
{
|
||||
foreach (var targetProperty in _properties.Except(_updatedList))
|
||||
{
|
||||
var optionAttr = Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(targetProperty);
|
||||
|
||||
var defaultValue = optionAttr?.DefaultValue;
|
||||
|
||||
if (defaultValue == null)
|
||||
continue;
|
||||
|
||||
if (SetPropertyValue(targetProperty, defaultValue.ToString(), _instance, optionAttr))
|
||||
_updatedList.Add(targetProperty);
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateInstance()
|
||||
{
|
||||
const char dash = '-';
|
||||
var propertyName = string.Empty;
|
||||
|
||||
foreach (var arg in _args)
|
||||
{
|
||||
var ignoreSetValue = string.IsNullOrWhiteSpace(propertyName);
|
||||
|
||||
if (ignoreSetValue)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(arg) || arg[0] != dash) continue;
|
||||
|
||||
propertyName = arg.Substring(1);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(propertyName) && propertyName[0] == dash)
|
||||
propertyName = propertyName.Substring(1);
|
||||
}
|
||||
|
||||
var targetProperty = TryGetProperty(propertyName);
|
||||
|
||||
if (targetProperty == null)
|
||||
{
|
||||
// Skip if the property is not found
|
||||
UnknownList.Add(propertyName);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ignoreSetValue && SetPropertyValue(targetProperty, arg, _instance))
|
||||
{
|
||||
_updatedList.Add(targetProperty);
|
||||
propertyName = string.Empty;
|
||||
}
|
||||
else if (targetProperty.PropertyType == typeof(bool))
|
||||
{
|
||||
// If the arg is a boolean property set it to true.
|
||||
targetProperty.SetValue(_instance, true);
|
||||
|
||||
_updatedList.Add(targetProperty);
|
||||
propertyName = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(propertyName))
|
||||
{
|
||||
UnknownList.Add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
private bool SetPropertyValue(
|
||||
PropertyInfo targetProperty,
|
||||
string propertyValueString,
|
||||
object result,
|
||||
ArgumentOptionAttribute optionAttr = null)
|
||||
{
|
||||
if (targetProperty.PropertyType.GetTypeInfo().IsEnum)
|
||||
{
|
||||
var parsedValue = Enum.Parse(
|
||||
targetProperty.PropertyType,
|
||||
propertyValueString,
|
||||
_settings.CaseInsensitiveEnumValues);
|
||||
|
||||
targetProperty.SetValue(result, Enum.ToObject(targetProperty.PropertyType, parsedValue));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return targetProperty.PropertyType.IsArray
|
||||
? targetProperty.TrySetArray(propertyValueString.Split(optionAttr?.Separator ?? ','), result)
|
||||
: targetProperty.TrySetBasicType(propertyValueString, result);
|
||||
}
|
||||
|
||||
private PropertyInfo TryGetProperty(string propertyName)
|
||||
=> _properties.FirstOrDefault(p =>
|
||||
string.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.LongName, propertyName, _settings.NameComparer) ||
|
||||
string.Equals(Runtime.AttributeCache.RetrieveOne<ArgumentOptionAttribute>(p)?.ShortName, propertyName, _settings.NameComparer));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Attributes;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// </summary>
|
||||
public partial class ArgumentParser
|
||||
{
|
||||
private sealed class TypeResolver<T>
|
||||
{
|
||||
private readonly string _selectedVerb;
|
||||
|
||||
private PropertyInfo[] _properties;
|
||||
|
||||
public TypeResolver(string selectedVerb)
|
||||
{
|
||||
_selectedVerb = selectedVerb;
|
||||
}
|
||||
|
||||
public PropertyInfo[] GetProperties() => _properties?.Any() == true ? _properties : null;
|
||||
|
||||
public object GetOptionsObject(T instance)
|
||||
{
|
||||
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true).ToArray();
|
||||
|
||||
if (!_properties.Any(x => x.GetCustomAttributes(typeof(VerbOptionAttribute), false).Any()))
|
||||
return instance;
|
||||
|
||||
var selectedVerb = string.IsNullOrWhiteSpace(_selectedVerb)
|
||||
? null
|
||||
: _properties.FirstOrDefault(x =>
|
||||
Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x).Name.Equals(_selectedVerb));
|
||||
|
||||
if (selectedVerb == null) return null;
|
||||
|
||||
var type = instance.GetType();
|
||||
|
||||
var verbProperty = type.GetProperty(selectedVerb.Name);
|
||||
|
||||
if (verbProperty?.GetValue(instance) == null)
|
||||
{
|
||||
var propertyInstance = Activator.CreateInstance(selectedVerb.PropertyType);
|
||||
verbProperty?.SetValue(instance, propertyInstance);
|
||||
}
|
||||
|
||||
_properties = Runtime.PropertyTypeCache.RetrieveAllProperties(selectedVerb.PropertyType, true)
|
||||
.ToArray();
|
||||
|
||||
return verbProperty?.GetValue(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
230
Unosquare.Swan.Lite/Components/ArgumentParser.cs
Normal file
230
Unosquare.Swan.Lite/Components/ArgumentParser.cs
Normal file
@ -0,0 +1,230 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Attributes;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Provides methods to parse command line arguments.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to parse CLI arguments into objects.
|
||||
/// <code>
|
||||
/// class Example
|
||||
/// {
|
||||
/// using System;
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// static void Main(string[] args)
|
||||
/// {
|
||||
/// // create an instance of the Options class
|
||||
/// var options = new Options();
|
||||
///
|
||||
/// // parse the supplied command-line arguments into the options object
|
||||
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
|
||||
/// }
|
||||
///
|
||||
/// class Options
|
||||
/// {
|
||||
/// [ArgumentOption('v', "verbose", HelpText = "Set verbose mode.")]
|
||||
/// public bool Verbose { get; set; }
|
||||
///
|
||||
/// [ArgumentOption('u', Required = true, HelpText = "Set user name.")]
|
||||
/// public string Username { get; set; }
|
||||
///
|
||||
/// [ArgumentOption('n', "names", Separator = ',',
|
||||
/// Required = true, HelpText = "A list of files separated by a comma")]
|
||||
/// public string[] Files { get; set; }
|
||||
///
|
||||
/// [ArgumentOption('p', "port", DefaultValue = 22, HelpText = "Set port.")]
|
||||
/// public int Port { get; set; }
|
||||
///
|
||||
/// [ArgumentOption("color", DefaultValue = ConsoleColor.Red,
|
||||
/// HelpText = "Set a color.")]
|
||||
/// public ConsoleColor Color { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following code describes how to parse CLI verbs.
|
||||
/// <code>
|
||||
/// class Example2
|
||||
/// {
|
||||
/// using Unosquare.Swan;
|
||||
/// using Unosquare.Swan.Attributes;
|
||||
///
|
||||
/// static void Main(string[] args)
|
||||
/// {
|
||||
/// // create an instance of the VerbOptions class
|
||||
/// var options = new VerbOptions();
|
||||
///
|
||||
/// // parse the supplied command-line arguments into the options object
|
||||
/// var res = Runtime.ArgumentParser.ParseArguments(args, options);
|
||||
///
|
||||
/// // if there were no errors parsing
|
||||
/// if (res)
|
||||
/// {
|
||||
/// if(options.Run != null)
|
||||
/// {
|
||||
/// // run verb was selected
|
||||
/// }
|
||||
///
|
||||
/// if(options.Print != null)
|
||||
/// {
|
||||
/// // print verb was selected
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // flush all error messages
|
||||
/// Terminal.Flush();
|
||||
/// }
|
||||
///
|
||||
/// class VerbOptions
|
||||
/// {
|
||||
/// [VerbOption("run", HelpText = "Run verb.")]
|
||||
/// public RunVerbOption Run { get; set; }
|
||||
///
|
||||
/// [VerbOption("print", HelpText = "Print verb.")]
|
||||
/// public PrintVerbOption Print { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// class RunVerbOption
|
||||
/// {
|
||||
/// [ArgumentOption('o', "outdir", HelpText = "Output directory",
|
||||
/// DefaultValue = "", Required = false)]
|
||||
/// public string OutDir { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// class PrintVerbOption
|
||||
/// {
|
||||
/// [ArgumentOption('t', "text", HelpText = "Text to print",
|
||||
/// DefaultValue = "", Required = false)]
|
||||
/// public string Text { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public partial class ArgumentParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser"/> class.
|
||||
/// </summary>
|
||||
public ArgumentParser()
|
||||
: this(new ArgumentParserSettings())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ArgumentParser" /> class,
|
||||
/// configurable with <see cref="ArgumentParserSettings" /> using a delegate.
|
||||
/// </summary>
|
||||
/// <param name="parseSettings">The parse settings.</param>
|
||||
public ArgumentParser(ArgumentParserSettings parseSettings)
|
||||
{
|
||||
Settings = parseSettings ?? throw new ArgumentNullException(nameof(parseSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance that implements <see cref="ArgumentParserSettings" /> in use.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The settings.
|
||||
/// </value>
|
||||
public ArgumentParserSettings Settings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string array of command line arguments constructing values in an instance of type <typeparamref name="T" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the options.</typeparam>
|
||||
/// <param name="args">The arguments.</param>
|
||||
/// <param name="instance">The instance.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if was converted successfully; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// The exception that is thrown when a null reference (Nothing in Visual Basic)
|
||||
/// is passed to a method that does not accept it as a valid argument.
|
||||
/// </exception>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// The exception that is thrown when a method call is invalid for the object's current state.
|
||||
/// </exception>
|
||||
public bool ParseArguments<T>(IEnumerable<string> args, T instance)
|
||||
{
|
||||
if (args == null)
|
||||
throw new ArgumentNullException(nameof(args));
|
||||
|
||||
if (Equals(instance, default(T)))
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
|
||||
var typeResolver = new TypeResolver<T>(args.FirstOrDefault());
|
||||
var options = typeResolver.GetOptionsObject(instance);
|
||||
|
||||
if (options == null)
|
||||
{
|
||||
ReportUnknownVerb<T>();
|
||||
return false;
|
||||
}
|
||||
|
||||
var properties = typeResolver.GetProperties();
|
||||
|
||||
if (properties == null)
|
||||
throw new InvalidOperationException($"Type {typeof(T).Name} is not valid");
|
||||
|
||||
var validator = new Validator(properties, args, options, Settings);
|
||||
|
||||
if (validator.IsValid())
|
||||
return true;
|
||||
|
||||
ReportIssues(validator);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ReportUnknownVerb<T>()
|
||||
{
|
||||
"No verb was specified".WriteLine(ConsoleColor.Red);
|
||||
"Valid verbs:".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
Runtime.PropertyTypeCache.RetrieveAllProperties<T>(true)
|
||||
.Select(x => Runtime.AttributeCache.RetrieveOne<VerbOptionAttribute>(x))
|
||||
.Where(x => x != null)
|
||||
.ToList()
|
||||
.ForEach(x => x.ToString().WriteLine(ConsoleColor.Cyan));
|
||||
}
|
||||
|
||||
private void ReportIssues(Validator validator)
|
||||
{
|
||||
#if !NETSTANDARD1_3
|
||||
if (Settings.WriteBanner)
|
||||
Runtime.WriteWelcomeBanner();
|
||||
#endif
|
||||
|
||||
var options = validator.GetPropertiesOptions();
|
||||
|
||||
foreach (var option in options)
|
||||
{
|
||||
string.Empty.WriteLine();
|
||||
|
||||
// TODO: If Enum list values
|
||||
var shortName = string.IsNullOrWhiteSpace(option.ShortName) ? string.Empty : $"-{option.ShortName}";
|
||||
var longName = string.IsNullOrWhiteSpace(option.LongName) ? string.Empty : $"--{option.LongName}";
|
||||
var comma = string.IsNullOrWhiteSpace(shortName) || string.IsNullOrWhiteSpace(longName)
|
||||
? string.Empty
|
||||
: ", ";
|
||||
var defaultValue = option.DefaultValue == null ? string.Empty : $"(Default: {option.DefaultValue}) ";
|
||||
|
||||
$" {shortName}{comma}{longName}\t\t{defaultValue}{option.HelpText}".WriteLine(ConsoleColor.Cyan);
|
||||
}
|
||||
|
||||
string.Empty.WriteLine();
|
||||
" --help\t\tDisplay this help screen.".WriteLine(ConsoleColor.Cyan);
|
||||
|
||||
if (validator.UnknownList.Any())
|
||||
$"Unknown arguments: {string.Join(", ", validator.UnknownList)}".WriteLine(ConsoleColor.Red);
|
||||
|
||||
if (validator.RequiredList.Any())
|
||||
$"Required arguments: {string.Join(", ", validator.RequiredList)}".WriteLine(ConsoleColor.Red);
|
||||
}
|
||||
}
|
||||
}
|
53
Unosquare.Swan.Lite/Components/ArgumentParserSettings.cs
Normal file
53
Unosquare.Swan.Lite/Components/ArgumentParserSettings.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides settings for <see cref="ArgumentParser"/>.
|
||||
/// Based on CommandLine (Copyright 2005-2015 Giacomo Stelluti Scala and Contributors.).
|
||||
/// </summary>
|
||||
public class ArgumentParserSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [write banner].
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [write banner]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool WriteBanner { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons.
|
||||
/// Note that case insensitivity only applies to <i>parameters</i>, not the values
|
||||
/// assigned to them (for example, enum parsing).
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case sensitive]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaseSensitive { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether perform case sensitive comparisons of <i>values</i>.
|
||||
/// Note that case insensitivity only applies to <i>values</i>, not the parameters.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [case insensitive enum values]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CaseInsensitiveEnumValues { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the parser shall move on to the next argument and ignore the given argument if it
|
||||
/// encounter an unknown arguments.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> to allow parsing the arguments with different class options that do not have all the arguments.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// This allows fragmented version class parsing, useful for project with add-on where add-ons also requires command line arguments but
|
||||
/// when these are unknown by the main program at build time.
|
||||
/// </remarks>
|
||||
public bool IgnoreUnknownArguments { get; set; } = true;
|
||||
|
||||
internal StringComparison NameComparer => CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
}
|
130
Unosquare.Swan.Lite/Components/Benchmark.cs
Normal file
130
Unosquare.Swan.Lite/Components/Benchmark.cs
Normal file
@ -0,0 +1,130 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// A simple benchmarking class.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code demonstrates how to create a simple benchmark.
|
||||
/// <code>
|
||||
/// namespace Examples.Benchmark.Simple
|
||||
/// {
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// public class SimpleBenchmark
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// using (Benchmark.Start("Test"))
|
||||
/// {
|
||||
/// // do some logic in here
|
||||
/// }
|
||||
///
|
||||
/// // dump results into a string
|
||||
/// var results = Benchmark.Dump();
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class Benchmark
|
||||
{
|
||||
private static readonly object SyncLock = new object();
|
||||
private static readonly Dictionary<string, List<TimeSpan>> Measures = new Dictionary<string, List<TimeSpan>>();
|
||||
|
||||
/// <summary>
|
||||
/// Starts measuring with the given identifier.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier.</param>
|
||||
/// <returns>A disposable object that when disposed, adds a benchmark result.</returns>
|
||||
public static IDisposable Start(string identifier) => new BenchmarkUnit(identifier);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs the benchmark statistics.
|
||||
/// </summary>
|
||||
/// <returns>A string containing human-readable statistics.</returns>
|
||||
public static string Dump()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
lock (SyncLock)
|
||||
{
|
||||
foreach (var kvp in Measures)
|
||||
{
|
||||
builder.Append($"BID: {kvp.Key,-30} | ")
|
||||
.Append($"CNT: {kvp.Value.Count,6} | ")
|
||||
.Append($"AVG: {kvp.Value.Average(t => t.TotalMilliseconds),8:0.000} ms. | ")
|
||||
.Append($"MAX: {kvp.Value.Max(t => t.TotalMilliseconds),8:0.000} ms. | ")
|
||||
.Append($"MIN: {kvp.Value.Min(t => t.TotalMilliseconds),8:0.000} ms. | ")
|
||||
.Append(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified result to the given identifier.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier.</param>
|
||||
/// <param name="elapsed">The elapsed.</param>
|
||||
private static void Add(string identifier, TimeSpan elapsed)
|
||||
{
|
||||
lock (SyncLock)
|
||||
{
|
||||
if (Measures.ContainsKey(identifier) == false)
|
||||
Measures[identifier] = new List<TimeSpan>(1024 * 1024);
|
||||
|
||||
Measures[identifier].Add(elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a disposable benchmark unit.
|
||||
/// </summary>
|
||||
/// <seealso cref="IDisposable" />
|
||||
private sealed class BenchmarkUnit : IDisposable
|
||||
{
|
||||
private readonly string _identifier;
|
||||
private bool _isDisposed; // To detect redundant calls
|
||||
private Stopwatch _stopwatch = new Stopwatch();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BenchmarkUnit" /> class.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier.</param>
|
||||
public BenchmarkUnit(string identifier)
|
||||
{
|
||||
_identifier = identifier;
|
||||
_stopwatch.Start();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="alsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
private void Dispose(bool alsoManaged)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
if (alsoManaged)
|
||||
{
|
||||
Add(_identifier, _stopwatch.Elapsed);
|
||||
_stopwatch?.Stop();
|
||||
}
|
||||
|
||||
_stopwatch = null;
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
Unosquare.Swan.Lite/Components/CollectionCacheRepository.cs
Normal file
44
Unosquare.Swan.Lite/Components/CollectionCacheRepository.cs
Normal file
@ -0,0 +1,44 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// A thread-safe collection cache repository for types.
|
||||
/// </summary>
|
||||
/// <typeparam name="TValue">The type of member to cache.</typeparam>
|
||||
public class CollectionCacheRepository<TValue>
|
||||
{
|
||||
private readonly Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>> _data =
|
||||
new Lazy<ConcurrentDictionary<Type, IEnumerable<TValue>>>(() =>
|
||||
new ConcurrentDictionary<Type, IEnumerable<TValue>>(), true);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the cache contains the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <returns><c>true</c> if the cache contains the key, otherwise <c>false</c>.</returns>
|
||||
public bool ContainsKey(Type key) => _data.Value.ContainsKey(key);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the properties stored for the specified type.
|
||||
/// If the properties are not available, it calls the factory method to retrieve them
|
||||
/// and returns them as an array of PropertyInfo.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="factory">The factory.</param>
|
||||
/// <returns>
|
||||
/// An array of the properties stored for the specified type.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public IEnumerable<TValue> Retrieve(Type key, Func<Type, IEnumerable<TValue>> factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new ArgumentNullException(nameof(factory));
|
||||
|
||||
return _data.Value.GetOrAdd(key, k => factory.Invoke(k).Where(item => item != null));
|
||||
}
|
||||
}
|
||||
}
|
171
Unosquare.Swan.Lite/Components/EnumHelper.cs
Normal file
171
Unosquare.Swan.Lite/Components/EnumHelper.cs
Normal file
@ -0,0 +1,171 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provide Enumerations helpers with internal cache.
|
||||
/// </summary>
|
||||
public class EnumHelper
|
||||
: SingletonBase<CollectionCacheRepository<Tuple<string, object>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the names and enumerators from a specific Enum type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the attribute to be retrieved.</typeparam>
|
||||
/// <returns>A tuple of enumerator names and their value stored for the specified type.</returns>
|
||||
public static IEnumerable<Tuple<string, object>> Retrieve<T>()
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
return Instance.Retrieve(typeof(T), t => Enum.GetValues(t)
|
||||
.Cast<object>()
|
||||
.Select(item => Tuple.Create(Enum.GetName(t, item), item)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached items with the enum item value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
|
||||
/// <returns>
|
||||
/// A collection of Type/Tuple pairs
|
||||
/// that represents items with the enum item value.
|
||||
/// </returns>
|
||||
public static IEnumerable<Tuple<int, string>> GetItemsWithValue<T>(bool humanize = true)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
return Retrieve<T>()
|
||||
.Select(x => Tuple.Create((int) x.Item2, humanize ? x.Item1.Humanize() : x.Item1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<int> GetFlagValues<TEnum>(int value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (int) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<long> GetFlagValues<TEnum>(long value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (long) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag values.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <returns>
|
||||
/// A list of values in the flag.
|
||||
/// </returns>
|
||||
public static IEnumerable<byte> GetFlagValues<TEnum>(byte value, bool ignoreZero = false)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.Select(x => (byte) x.Item2)
|
||||
.When(() => ignoreZero, q => q.Where(f => f != 0))
|
||||
.Where(x => (x & value) == x);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag names.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">the value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
|
||||
/// <returns>
|
||||
/// A list of flag names.
|
||||
/// </returns>
|
||||
public static IEnumerable<string> GetFlagNames<TEnum>(int value, bool ignoreZero = false, bool humanize = true)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.When(() => ignoreZero, q => q.Where(f => (int) f.Item2 != 0))
|
||||
.Where(x => ((int) x.Item2 & value) == (int) x.Item2)
|
||||
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag names.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
|
||||
/// <returns>
|
||||
/// A list of flag names.
|
||||
/// </returns>
|
||||
public static IEnumerable<string> GetFlagNames<TEnum>(long value, bool ignoreZero = false, bool humanize = true)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.When(() => ignoreZero, q => q.Where(f => (long) f.Item2 != 0))
|
||||
.Where(x => ((long) x.Item2 & value) == (long) x.Item2)
|
||||
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flag names.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="ignoreZero">if set to <c>true</c> [ignore zero].</param>
|
||||
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
|
||||
/// <returns>
|
||||
/// A list of flag names.
|
||||
/// </returns>
|
||||
public static IEnumerable<string> GetFlagNames<TEnum>(byte value, bool ignoreZero = false, bool humanize = true)
|
||||
where TEnum : struct, IConvertible
|
||||
{
|
||||
return Retrieve<TEnum>()
|
||||
.When(() => ignoreZero, q => q.Where(f => (byte) f.Item2 != 0))
|
||||
.Where(x => ((byte) x.Item2 & value) == (byte) x.Item2)
|
||||
.Select(x => humanize ? x.Item1.Humanize() : x.Item1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cached items with the enum item index.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="humanize">if set to <c>true</c> [humanize].</param>
|
||||
/// <returns>
|
||||
/// A collection of Type/Tuple pairs that represents items with the enum item value.
|
||||
/// </returns>
|
||||
public static IEnumerable<Tuple<int, string>> GetItemsWithIndex<T>(bool humanize = true)
|
||||
where T : struct, IConvertible
|
||||
{
|
||||
var i = 0;
|
||||
|
||||
return Retrieve<T>()
|
||||
.Select(x => Tuple.Create(i++, humanize ? x.Item1.Humanize() : x.Item1));
|
||||
}
|
||||
}
|
||||
}
|
193
Unosquare.Swan.Lite/Components/ObjectComparer.cs
Normal file
193
Unosquare.Swan.Lite/Components/ObjectComparer.cs
Normal file
@ -0,0 +1,193 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a quick object comparer using the public properties of an object
|
||||
/// or the public members in a structure.
|
||||
/// </summary>
|
||||
public static class ObjectComparer
|
||||
{
|
||||
/// <summary>
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the variables are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreEqual<T>(T left, T right) => AreEqual(left, right, typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two variables of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the variables are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
if (Definitions.BasicTypesInfo.ContainsKey(targetType))
|
||||
return Equals(left, right);
|
||||
|
||||
if (targetType.IsValueType() || targetType.IsArray)
|
||||
return AreStructsEqual(left, right, targetType);
|
||||
|
||||
return AreObjectsEqual(left, right, targetType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of objects to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreObjectsEqual<T>(T left, T right)
|
||||
where T : class
|
||||
{
|
||||
return AreObjectsEqual(left, right, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two objects of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns><c>true</c> if the objects are equal; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreObjectsEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
var properties = Runtime.PropertyTypeCache.RetrieveAllProperties(targetType).ToArray();
|
||||
|
||||
foreach (var propertyTarget in properties)
|
||||
{
|
||||
var targetPropertyGetMethod = propertyTarget.GetCacheGetMethod();
|
||||
|
||||
if (propertyTarget.PropertyType.IsArray)
|
||||
{
|
||||
var leftObj = targetPropertyGetMethod(left) as IEnumerable;
|
||||
var rightObj = targetPropertyGetMethod(right) as IEnumerable;
|
||||
|
||||
if (!AreEnumerationsEquals(leftObj, rightObj))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structs to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns><c>true</c> if the structs are equal; otherwise, <c>false</c>.</returns>
|
||||
public static bool AreStructsEqual<T>(T left, T right)
|
||||
where T : struct
|
||||
{
|
||||
return AreStructsEqual(left, right, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two structures of the same type are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the structs are equal; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">targetType.</exception>
|
||||
public static bool AreStructsEqual(object left, object right, Type targetType)
|
||||
{
|
||||
if (targetType == null)
|
||||
throw new ArgumentNullException(nameof(targetType));
|
||||
|
||||
var fields = new List<MemberInfo>(Runtime.FieldTypeCache.RetrieveAllFields(targetType))
|
||||
.Union(Runtime.PropertyTypeCache.RetrieveAllProperties(targetType));
|
||||
|
||||
foreach (var targetMember in fields)
|
||||
{
|
||||
switch (targetMember)
|
||||
{
|
||||
case FieldInfo field:
|
||||
if (Equals(field.GetValue(left), field.GetValue(right)) == false)
|
||||
return false;
|
||||
break;
|
||||
case PropertyInfo property:
|
||||
var targetPropertyGetMethod = property.GetCacheGetMethod();
|
||||
|
||||
if (targetPropertyGetMethod != null &&
|
||||
!Equals(targetPropertyGetMethod(left), targetPropertyGetMethod(right)))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare if two enumerables are equal.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enums to compare.</typeparam>
|
||||
/// <param name="left">The left.</param>
|
||||
/// <param name="right">The right.</param>
|
||||
/// <returns>
|
||||
/// True if two specified types are equal; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// left
|
||||
/// or
|
||||
/// right.
|
||||
/// </exception>
|
||||
public static bool AreEnumerationsEquals<T>(T left, T right)
|
||||
where T : IEnumerable
|
||||
{
|
||||
if (Equals(left, default(T)))
|
||||
throw new ArgumentNullException(nameof(left));
|
||||
|
||||
if (Equals(right, default(T)))
|
||||
throw new ArgumentNullException(nameof(right));
|
||||
|
||||
var leftEnumerable = left.Cast<object>().ToArray();
|
||||
var rightEnumerable = right.Cast<object>().ToArray();
|
||||
|
||||
if (leftEnumerable.Length != rightEnumerable.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < leftEnumerable.Length; i++)
|
||||
{
|
||||
var leftEl = leftEnumerable[i];
|
||||
var rightEl = rightEnumerable[i];
|
||||
|
||||
if (!AreEqual(leftEl, rightEl, leftEl.GetType()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
116
Unosquare.Swan.Lite/Components/ObjectMap.cs
Normal file
116
Unosquare.Swan.Lite/Components/ObjectMap.cs
Normal file
@ -0,0 +1,116 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <seealso cref="Unosquare.Swan.Abstractions.IObjectMap" />
|
||||
public class ObjectMap<TSource, TDestination> : IObjectMap
|
||||
{
|
||||
internal ObjectMap(IEnumerable<PropertyInfo> intersect)
|
||||
{
|
||||
SourceType = typeof(TSource);
|
||||
DestinationType = typeof(TDestination);
|
||||
Map = intersect.ToDictionary(
|
||||
property => DestinationType.GetProperty(property.Name),
|
||||
property => new List<PropertyInfo> {SourceType.GetProperty(property.Name)});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<PropertyInfo, List<PropertyInfo>> Map { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type SourceType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type DestinationType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maps the property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <param name="sourceProperty">The source property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
public ObjectMap<TSource, TDestination> MapProperty
|
||||
<TDestinationProperty, TSourceProperty>(
|
||||
Expression<Func<TDestination, TDestinationProperty>> destinationProperty,
|
||||
Expression<Func<TSource, TSourceProperty>> sourceProperty)
|
||||
{
|
||||
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if (propertyDestinationInfo == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
var sourceMembers = GetSourceMembers(sourceProperty);
|
||||
|
||||
if (sourceMembers.Any() == false)
|
||||
{
|
||||
throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
|
||||
}
|
||||
|
||||
// reverse order
|
||||
sourceMembers.Reverse();
|
||||
Map[propertyDestinationInfo] = sourceMembers;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the map property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.Exception">Invalid destination expression.</exception>
|
||||
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(
|
||||
Expression<Func<TDestination, TDestinationProperty>> destinationProperty)
|
||||
{
|
||||
var propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if (propertyDestinationInfo == null)
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
|
||||
if (Map.ContainsKey(propertyDestinationInfo))
|
||||
{
|
||||
Map.Remove(propertyDestinationInfo);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty)
|
||||
{
|
||||
var sourceMembers = new List<PropertyInfo>();
|
||||
var initialExpression = sourceProperty.Body as MemberExpression;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var propertySourceInfo = initialExpression?.Member as PropertyInfo;
|
||||
|
||||
if (propertySourceInfo == null) break;
|
||||
sourceMembers.Add(propertySourceInfo);
|
||||
initialExpression = initialExpression.Expression as MemberExpression;
|
||||
}
|
||||
|
||||
return sourceMembers;
|
||||
}
|
||||
}
|
||||
}
|
411
Unosquare.Swan.Lite/Components/ObjectMapper.cs
Normal file
411
Unosquare.Swan.Lite/Components/ObjectMapper.cs
Normal file
@ -0,0 +1,411 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an AutoMapper-like object to map from one object type
|
||||
/// to another using defined properties map or using the default behaviour
|
||||
/// to copy same named properties from one object to another.
|
||||
///
|
||||
/// The extension methods like CopyPropertiesTo use the default behaviour.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code explains how to map an object's properties into an instance of type T.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// var obj = new { Name = "Søren", Age = 42 };
|
||||
///
|
||||
/// var person = Runtime.ObjectMapper.Map<Person>(obj);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following code explains how to explicitly map certain properties.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class User
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public Role Role { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// public class Role
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// class UserDto
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public string Role { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // create a User object
|
||||
/// var person =
|
||||
/// new User { Name = "Phillip", Role = new Role { Name = "Admin" } };
|
||||
///
|
||||
/// // create an Object Mapper
|
||||
/// var mapper = new ObjectMapper();
|
||||
///
|
||||
/// // map the User's Role.Name to UserDto's Role
|
||||
/// mapper.CreateMap<User, UserDto>()
|
||||
/// .MapProperty(d => d.Role, x => x.Role.Name);
|
||||
///
|
||||
/// // apply the previous map and retrieve a UserDto object
|
||||
/// var destination = mapper.Map<UserDto>(person);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectMapper
|
||||
{
|
||||
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static int Copy(
|
||||
object source,
|
||||
object target,
|
||||
string[] propertiesToCopy = null,
|
||||
string[] ignoreProperties = null)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
return Copy(
|
||||
target,
|
||||
propertiesToCopy,
|
||||
ignoreProperties,
|
||||
GetSourceMap(source));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the specified source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Copied properties count.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// source
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static int Copy(
|
||||
IDictionary<string, object> source,
|
||||
object target,
|
||||
string[] propertiesToCopy = null,
|
||||
string[] ignoreProperties = null)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
return Copy(
|
||||
target,
|
||||
propertiesToCopy,
|
||||
ignoreProperties,
|
||||
source.ToDictionary(
|
||||
x => x.Key.ToLowerInvariant(),
|
||||
x => new TypeValuePair(typeof(object), x.Value)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// You can't create an existing map
|
||||
/// or
|
||||
/// Types doesn't match.
|
||||
/// </exception>
|
||||
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>()
|
||||
{
|
||||
if (_maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination)))
|
||||
{
|
||||
throw new InvalidOperationException("You can't create an existing map");
|
||||
}
|
||||
|
||||
var sourceType = Runtime.PropertyTypeCache.RetrieveAllProperties<TSource>(true);
|
||||
var destinationType = Runtime.PropertyTypeCache.RetrieveAllProperties<TDestination>(true);
|
||||
|
||||
var intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
|
||||
|
||||
if (intersect.Any() == false)
|
||||
{
|
||||
throw new InvalidOperationException("Types doesn't match");
|
||||
}
|
||||
|
||||
var map = new ObjectMap<TSource, TDestination>(intersect);
|
||||
|
||||
_maps.Add(map);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the specified source.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
|
||||
/// <returns>
|
||||
/// A new instance of the map.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
|
||||
public TDestination Map<TDestination>(object source, bool autoResolve = true)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var destination = Activator.CreateInstance<TDestination>();
|
||||
var map = _maps
|
||||
.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
|
||||
|
||||
if (map != null)
|
||||
{
|
||||
foreach (var property in map.Map)
|
||||
{
|
||||
var finalSource = property.Value.Aggregate(source,
|
||||
(current, sourceProperty) => sourceProperty.GetValue(current));
|
||||
|
||||
property.Key.SetValue(destination, finalSource);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!autoResolve)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
|
||||
}
|
||||
|
||||
// Missing mapping, try to use default behavior
|
||||
Copy(source, destination);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static int Copy(
|
||||
object target,
|
||||
IEnumerable<string> propertiesToCopy,
|
||||
IEnumerable<string> ignoreProperties,
|
||||
Dictionary<string, TypeValuePair> sourceProperties)
|
||||
{
|
||||
// Filter properties
|
||||
var requiredProperties = propertiesToCopy?
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p.ToLowerInvariant());
|
||||
|
||||
var ignoredProperties = ignoreProperties?
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.Select(p => p.ToLowerInvariant());
|
||||
|
||||
var properties = Runtime.PropertyTypeCache
|
||||
.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite);
|
||||
|
||||
return properties
|
||||
.Select(x => x.Name)
|
||||
.Distinct()
|
||||
.ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x))
|
||||
.Where(x => sourceProperties.Keys.Contains(x.Key))
|
||||
.When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key)))
|
||||
.When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key)))
|
||||
.ToDictionary(x => x.Value, x => sourceProperties[x.Key])
|
||||
.Sum(x => TrySetValue(x, target) ? 1 : 0);
|
||||
}
|
||||
|
||||
private static bool TrySetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetValue(property, target);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetValue(KeyValuePair<PropertyInfo, TypeValuePair> property, object target)
|
||||
{
|
||||
if (property.Value.Type.GetTypeInfo().IsEnum)
|
||||
{
|
||||
property.Key.SetValue(target,
|
||||
Enum.ToObject(property.Key.PropertyType, property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!property.Value.Type.IsValueType() && property.Key.PropertyType == property.Value.Type)
|
||||
{
|
||||
property.Key.SetValue(target, GetValue(property.Value.Value, property.Key.PropertyType));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.Key.PropertyType == typeof(bool))
|
||||
{
|
||||
property.Key.SetValue(target,
|
||||
Convert.ToBoolean(property.Value.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
property.Key.TrySetBasicType(property.Value.Value, target);
|
||||
}
|
||||
|
||||
private static object GetValue(object source, Type targetType)
|
||||
{
|
||||
if (source == null)
|
||||
return null;
|
||||
|
||||
object target = null;
|
||||
|
||||
source.CreateTarget(targetType, false, ref target);
|
||||
|
||||
switch (source)
|
||||
{
|
||||
case string _:
|
||||
target = source;
|
||||
break;
|
||||
case IList sourceList when target is Array targetArray:
|
||||
for (var i = 0; i < sourceList.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
targetArray.SetValue(
|
||||
sourceList[i].GetType().IsValueType()
|
||||
? sourceList[i]
|
||||
: sourceList[i].CopyPropertiesToNew<object>(), i);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case IList sourceList when target is IList targetList:
|
||||
var addMethod = targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(Formatters.Json.AddMethodName) && m.IsPublic &&
|
||||
m.GetParameters().Length == 1);
|
||||
|
||||
if (addMethod == null) return target;
|
||||
|
||||
foreach (var item in sourceList)
|
||||
{
|
||||
try
|
||||
{
|
||||
targetList.Add(item.GetType().IsValueType()
|
||||
? item
|
||||
: item.CopyPropertiesToNew<object>());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
source.CopyPropertiesTo(target);
|
||||
break;
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static Dictionary<string, TypeValuePair> GetSourceMap(object source)
|
||||
{
|
||||
// select distinct properties because they can be duplicated by inheritance
|
||||
var sourceProperties = Runtime.PropertyTypeCache
|
||||
.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead)
|
||||
.ToArray();
|
||||
|
||||
return sourceProperties
|
||||
.Select(x => x.Name)
|
||||
.Distinct()
|
||||
.ToDictionary(
|
||||
x => x.ToLowerInvariant(),
|
||||
x => new TypeValuePair(sourceProperties.First(y => y.Name == x).PropertyType,
|
||||
sourceProperties.First(y => y.Name == x).GetValue(source)));
|
||||
}
|
||||
|
||||
internal class TypeValuePair
|
||||
{
|
||||
public TypeValuePair(Type type, object value)
|
||||
{
|
||||
Type = type;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public Type Type { get; }
|
||||
|
||||
public object Value { get; }
|
||||
}
|
||||
|
||||
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo>
|
||||
{
|
||||
public bool Equals(PropertyInfo x, PropertyInfo y)
|
||||
=> x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
|
||||
|
||||
public int GetHashCode(PropertyInfo obj)
|
||||
=> obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
204
Unosquare.Swan.Lite/Components/ObjectValidator.cs
Normal file
204
Unosquare.Swan.Lite/Components/ObjectValidator.cs
Normal file
@ -0,0 +1,204 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object validator.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to perform a simple object validation.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// var obj = new ObjectValidator();
|
||||
///
|
||||
/// // Add a validation to the 'Simple' class with a custom error message
|
||||
/// obj.AddValidator<Simple>(x =>
|
||||
/// !string.IsNullOrEmpty(x.Name), "Name must not be empty");
|
||||
///
|
||||
/// // check if object is valid
|
||||
/// var res = obj.IsValid(new Simple { Name = "Name" });
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
///
|
||||
/// The following code shows of to validate an object with a custom validator and some attributes using the Runtime ObjectValidator singleton.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// // create an instance of ObjectValidator
|
||||
/// Runtime.ObjectValidator
|
||||
/// .AddValidator<Simple>(x =>
|
||||
/// !x.Name.Equals("Name"), "Name must not be 'Name'");
|
||||
///
|
||||
/// // validate object
|
||||
/// var res = Runtime.ObjectValidator
|
||||
/// .Validate(new Simple{ Name = "name", Number = 5, Email ="email@mail.com"})
|
||||
/// }
|
||||
///
|
||||
/// class Simple
|
||||
/// {
|
||||
/// [NotNull]
|
||||
/// public string Name { get; set; }
|
||||
///
|
||||
/// [Range(1, 10)]
|
||||
/// public int Number { get; set; }
|
||||
///
|
||||
/// [Email]
|
||||
/// public string Email { get; set; }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class ObjectValidator
|
||||
{
|
||||
private readonly ConcurrentDictionary<Type, List<Tuple<Delegate, string>>> _predicates =
|
||||
new ConcurrentDictionary<Type, List<Tuple<Delegate, string>>>();
|
||||
|
||||
/// <summary>
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns cref="ObjectValidationResult">A validation result. </returns>
|
||||
public ObjectValidationResult Validate<T>(T obj)
|
||||
{
|
||||
var errorList = new ObjectValidationResult();
|
||||
ValidateObject(obj, false, errorList.Add);
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates an object given the specified validators and attributes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified object is valid; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">obj.</exception>
|
||||
public bool IsValid<T>(T obj) => ValidateObject(obj);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a validator to a specific class.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <param name="predicate">The predicate that will be evaluated.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// predicate
|
||||
/// or
|
||||
/// message.
|
||||
/// </exception>
|
||||
public void AddValidator<T>(Predicate<T> predicate, string message)
|
||||
where T : class
|
||||
{
|
||||
if (predicate == null)
|
||||
throw new ArgumentNullException(nameof(predicate));
|
||||
|
||||
if (string.IsNullOrEmpty(message))
|
||||
throw new ArgumentNullException(message);
|
||||
|
||||
if (!_predicates.TryGetValue(typeof(T), out var existing))
|
||||
{
|
||||
existing = new List<Tuple<Delegate, string>>();
|
||||
_predicates[typeof(T)] = existing;
|
||||
}
|
||||
|
||||
existing.Add(Tuple.Create((Delegate) predicate, message));
|
||||
}
|
||||
|
||||
private bool ValidateObject<T>(T obj, bool returnOnError = true, Action<string, string> action = null)
|
||||
{
|
||||
if (Equals(obj, null))
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
if (_predicates.ContainsKey(typeof(T)))
|
||||
{
|
||||
foreach (var validation in _predicates[typeof(T)])
|
||||
{
|
||||
if ((bool) validation.Item1.DynamicInvoke(obj)) continue;
|
||||
|
||||
action?.Invoke(string.Empty, validation.Item2);
|
||||
if (returnOnError) return false;
|
||||
}
|
||||
}
|
||||
|
||||
var properties = Runtime.AttributeCache.RetrieveFromType<T>(typeof(IValidator));
|
||||
|
||||
foreach (var prop in properties)
|
||||
{
|
||||
foreach (var attribute in prop.Value)
|
||||
{
|
||||
var val = (IValidator) attribute;
|
||||
|
||||
if (val.IsValid(prop.Key.GetValue(obj, null))) continue;
|
||||
|
||||
action?.Invoke(prop.Key.Name, val.ErrorMessage);
|
||||
if (returnOnError) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation result containing all validation errors and their properties.
|
||||
/// </summary>
|
||||
public class ObjectValidationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of errors.
|
||||
/// </summary>
|
||||
public List<ValidationError> Errors { get; set; } = new List<ValidationError>();
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if there are no errors; otherwise, <c>false</c>.
|
||||
/// </summary>
|
||||
public bool IsValid => !Errors.Any();
|
||||
|
||||
/// <summary>
|
||||
/// Adds an error with a specified property name.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The property name.</param>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
public void Add(string propertyName, string errorMessage) =>
|
||||
Errors.Add(new ValidationError {ErrorMessage = errorMessage, PropertyName = propertyName});
|
||||
|
||||
/// <summary>
|
||||
/// Defines a validation error.
|
||||
/// </summary>
|
||||
public class ValidationError
|
||||
{
|
||||
/// <summary>
|
||||
/// The property name.
|
||||
/// </summary>
|
||||
public string PropertyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The message error.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
197
Unosquare.Swan.Lite/Components/SyncLockerFactory.cs
Normal file
197
Unosquare.Swan.Lite/Components/SyncLockerFactory.cs
Normal file
@ -0,0 +1,197 @@
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides factory methods to create synchronized reader-writer locks
|
||||
/// that support a generalized locking and releasing api and syntax.
|
||||
/// </summary>
|
||||
public static class SyncLockerFactory
|
||||
{
|
||||
#region Enums and Interfaces
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the locking operations.
|
||||
/// </summary>
|
||||
private enum LockHolderType
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines methods for releasing locks.
|
||||
/// </summary>
|
||||
private interface ISyncReleasable
|
||||
{
|
||||
/// <summary>
|
||||
/// Releases the writer lock.
|
||||
/// </summary>
|
||||
void ReleaseWriterLock();
|
||||
|
||||
/// <summary>
|
||||
/// Releases the reader lock.
|
||||
/// </summary>
|
||||
void ReleaseReaderLock();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Factory Methods
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a standard ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker Create() => new SyncLocker();
|
||||
#else
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a standard ReaderWriterLockSlim when
|
||||
/// running at NETSTANDARD 1.3.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker</returns>
|
||||
public static ISyncLocker Create() => new SyncLockerSlim();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock backed by a ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <returns>The synchronized locker.</returns>
|
||||
public static ISyncLocker CreateSlim() => new SyncLockerSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader-writer lock.
|
||||
/// </summary>
|
||||
/// <param name="useSlim">if set to <c>true</c> it uses the Slim version of a reader-writer lock.</param>
|
||||
/// <returns>The Sync Locker.</returns>
|
||||
public static ISyncLocker Create(bool useSlim) => useSlim ? CreateSlim() : Create();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Classes
|
||||
|
||||
/// <summary>
|
||||
/// The lock releaser. Calling the dispose method releases the lock entered by the parent SyncLocker.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
private sealed class SyncLockReleaser : IDisposable
|
||||
{
|
||||
private readonly ISyncReleasable _parent;
|
||||
private readonly LockHolderType _operation;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SyncLockReleaser"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="operation">The operation.</param>
|
||||
public SyncLockReleaser(ISyncReleasable parent, LockHolderType operation)
|
||||
{
|
||||
_parent = parent;
|
||||
_operation = operation;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
|
||||
if (_operation == LockHolderType.Read)
|
||||
_parent.ReleaseReaderLock();
|
||||
else
|
||||
_parent.ReleaseWriterLock();
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by a ReaderWriterLock.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLocker : ISyncLocker, ISyncReleasable
|
||||
{
|
||||
private bool _isDisposed;
|
||||
private ReaderWriterLock _locker = new ReaderWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock()
|
||||
{
|
||||
_locker?.AcquireReaderLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock()
|
||||
{
|
||||
_locker?.AcquireWriterLock(Timeout.Infinite);
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => _locker?.ReleaseWriterLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => _locker?.ReleaseReaderLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_locker?.ReleaseLock();
|
||||
_locker = null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// The Sync Locker backed by ReaderWriterLockSlim.
|
||||
/// </summary>
|
||||
/// <seealso cref="ISyncLocker" />
|
||||
/// <seealso cref="ISyncReleasable" />
|
||||
private sealed class SyncLockerSlim : ISyncLocker, ISyncReleasable
|
||||
{
|
||||
private bool _isDisposed;
|
||||
|
||||
private ReaderWriterLockSlim _locker
|
||||
= new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireReaderLock()
|
||||
{
|
||||
_locker?.EnterReadLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Read);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IDisposable AcquireWriterLock()
|
||||
{
|
||||
_locker?.EnterWriteLock();
|
||||
return new SyncLockReleaser(this, LockHolderType.Write);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseWriterLock() => _locker?.ExitWriteLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReleaseReaderLock() => _locker?.ExitReadLock();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
_locker?.Dispose();
|
||||
_locker = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
63
Unosquare.Swan.Lite/Components/TimerControl.cs
Normal file
63
Unosquare.Swan.Lite/Components/TimerControl.cs
Normal file
@ -0,0 +1,63 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Use this singleton to wait for a specific <c>TimeSpan</c> or time.
|
||||
///
|
||||
/// Internally this class will use a <c>Timer</c> and a <c>ManualResetEvent</c> to block until
|
||||
/// the time condition is satisfied.
|
||||
/// </summary>
|
||||
/// <seealso cref="SingletonBase{TimerControl}" />
|
||||
public class TimerControl : SingletonBase<TimerControl>
|
||||
{
|
||||
private readonly Timer _innerTimer;
|
||||
private readonly IWaitEvent _delayLock = WaitEventFactory.Create(true);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimerControl"/> class.
|
||||
/// </summary>
|
||||
protected TimerControl()
|
||||
{
|
||||
_innerTimer = new Timer(
|
||||
x =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_delayLock.Complete();
|
||||
_delayLock.Begin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
},
|
||||
null,
|
||||
0,
|
||||
15);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the time is elapsed.
|
||||
/// </summary>
|
||||
/// <param name="untilDate">The until date.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void WaitUntil(DateTime untilDate, CancellationToken ct = default)
|
||||
{
|
||||
while (!ct.IsCancellationRequested && DateTime.UtcNow < untilDate)
|
||||
_delayLock.Wait();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits the specified wait time.
|
||||
/// </summary>
|
||||
/// <param name="waitTime">The wait time.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public void Wait(TimeSpan waitTime, CancellationToken ct = default) =>
|
||||
WaitUntil(DateTime.UtcNow.Add(waitTime), ct);
|
||||
}
|
||||
}
|
||||
#endif
|
222
Unosquare.Swan.Lite/Components/WaitEventFactory.cs
Normal file
222
Unosquare.Swan.Lite/Components/WaitEventFactory.cs
Normal file
@ -0,0 +1,222 @@
|
||||
#if !NETSTANDARD1_3
|
||||
namespace Unosquare.Swan.Components
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a Manual Reset Event factory with a unified API.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following example shows how to use the WaitEventFactory class.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Components;
|
||||
///
|
||||
/// public class Example
|
||||
/// {
|
||||
/// // create a WaitEvent using the slim version
|
||||
/// private static readonly IWaitEvent waitEvent = WaitEventFactory.CreateSlim(false);
|
||||
///
|
||||
/// public static void Main()
|
||||
/// {
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(1);
|
||||
/// });
|
||||
///
|
||||
/// Task.Factory.StartNew(() =>
|
||||
/// {
|
||||
/// DoWork(2);
|
||||
/// });
|
||||
///
|
||||
/// // send first signal
|
||||
/// waitEvent.Complete();
|
||||
/// waitEvent.Begin();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
///
|
||||
/// // send second signal
|
||||
/// waitEvent.Complete();
|
||||
///
|
||||
/// Console.Readline();
|
||||
/// }
|
||||
///
|
||||
/// public static void DoWork(int taskNumber)
|
||||
/// {
|
||||
/// $"Data retrieved:{taskNumber}".WriteLine();
|
||||
/// waitEvent.Wait();
|
||||
///
|
||||
/// Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
/// $"All finished up {taskNumber}".WriteLine();
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static class WaitEventFactory
|
||||
{
|
||||
#region Factory Methods
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a standard ManualResetEvent.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(bool isCompleted) => new WaitEvent(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent CreateSlim(bool isCompleted) => new WaitEventSlim(isCompleted);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Wait Event backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if initially set to completed. Generally true.</param>
|
||||
/// <param name="useSlim">if set to <c>true</c> creates a slim version of the wait event.</param>
|
||||
/// <returns>The Wait Event.</returns>
|
||||
public static IWaitEvent Create(bool isCompleted, bool useSlim) => useSlim ? CreateSlim(isCompleted) : Create(isCompleted);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Backing Classes
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEvent.
|
||||
/// </summary>
|
||||
private class WaitEvent : IWaitEvent
|
||||
{
|
||||
private ManualResetEvent _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEvent(bool isCompleted)
|
||||
{
|
||||
_event = new ManualResetEvent(isCompleted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDisposed || _event == null)
|
||||
return false;
|
||||
|
||||
if (_event?.SafeWaitHandle?.IsClosed ?? true)
|
||||
return false;
|
||||
|
||||
return !(_event?.SafeWaitHandle?.IsInvalid ?? true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsValid == false)
|
||||
return true;
|
||||
|
||||
return _event?.WaitOne(0) ?? true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInProgress => !IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => _event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => _event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
IsDisposed = true;
|
||||
|
||||
_event?.Set();
|
||||
_event?.Dispose();
|
||||
_event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => _event?.WaitOne();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Wait(TimeSpan timeout) => _event?.WaitOne(timeout) ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines a WaitEvent backed by a ManualResetEventSlim.
|
||||
/// </summary>
|
||||
private class WaitEventSlim : IWaitEvent
|
||||
{
|
||||
private ManualResetEventSlim _event;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WaitEventSlim"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isCompleted">if set to <c>true</c> [is completed].</param>
|
||||
public WaitEventSlim(bool isCompleted)
|
||||
{
|
||||
_event = new ManualResetEventSlim(isCompleted);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDisposed || _event?.WaitHandle?.SafeWaitHandle == null) return false;
|
||||
|
||||
return !_event.WaitHandle.SafeWaitHandle.IsClosed && !_event.WaitHandle.SafeWaitHandle.IsInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsCompleted => IsValid == false || _event.IsSet;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsInProgress => !IsCompleted;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Begin() => _event?.Reset();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Complete() => _event?.Set();
|
||||
|
||||
/// <inheritdoc />
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (IsDisposed) return;
|
||||
IsDisposed = true;
|
||||
|
||||
_event?.Set();
|
||||
_event?.Dispose();
|
||||
_event = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Wait() => _event?.Wait();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Wait(TimeSpan timeout) => _event?.Wait(timeout) ?? true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endif
|
174
Unosquare.Swan.Lite/DateTimeSpan.cs
Normal file
174
Unosquare.Swan.Lite/DateTimeSpan.cs
Normal file
@ -0,0 +1,174 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a struct of DateTimeSpan to compare dates and get in
|
||||
/// separate fields the amount of time between those dates.
|
||||
///
|
||||
/// Based on https://stackoverflow.com/a/9216404/1096693.
|
||||
/// </summary>
|
||||
public struct DateTimeSpan
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeSpan"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="years">The years.</param>
|
||||
/// <param name="months">The months.</param>
|
||||
/// <param name="days">The days.</param>
|
||||
/// <param name="hours">The hours.</param>
|
||||
/// <param name="minutes">The minutes.</param>
|
||||
/// <param name="seconds">The seconds.</param>
|
||||
/// <param name="milliseconds">The milliseconds.</param>
|
||||
public DateTimeSpan(int years, int months, int days, int hours, int minutes, int seconds, int milliseconds)
|
||||
{
|
||||
Years = years;
|
||||
Months = months;
|
||||
Days = days;
|
||||
Hours = hours;
|
||||
Minutes = minutes;
|
||||
Seconds = seconds;
|
||||
Milliseconds = milliseconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the years.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The years.
|
||||
/// </value>
|
||||
public int Years { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the months.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The months.
|
||||
/// </value>
|
||||
public int Months { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the days.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The days.
|
||||
/// </value>
|
||||
public int Days { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hours.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The hours.
|
||||
/// </value>
|
||||
public int Hours { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minutes.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The minutes.
|
||||
/// </value>
|
||||
public int Minutes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the seconds.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The seconds.
|
||||
/// </value>
|
||||
public int Seconds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the milliseconds.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The milliseconds.
|
||||
/// </value>
|
||||
public int Milliseconds { get; }
|
||||
|
||||
internal static DateTimeSpan CompareDates(DateTime date1, DateTime date2)
|
||||
{
|
||||
if (date2 < date1)
|
||||
{
|
||||
var sub = date1;
|
||||
date1 = date2;
|
||||
date2 = sub;
|
||||
}
|
||||
|
||||
var current = date1;
|
||||
var years = 0;
|
||||
var months = 0;
|
||||
var days = 0;
|
||||
|
||||
var phase = Phase.Years;
|
||||
var span = new DateTimeSpan();
|
||||
var officialDay = current.Day;
|
||||
|
||||
while (phase != Phase.Done)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case Phase.Years:
|
||||
if (current.AddYears(years + 1) > date2)
|
||||
{
|
||||
phase = Phase.Months;
|
||||
current = current.AddYears(years);
|
||||
}
|
||||
else
|
||||
{
|
||||
years++;
|
||||
}
|
||||
|
||||
break;
|
||||
case Phase.Months:
|
||||
if (current.AddMonths(months + 1) > date2)
|
||||
{
|
||||
phase = Phase.Days;
|
||||
current = current.AddMonths(months);
|
||||
if (current.Day < officialDay &&
|
||||
officialDay <= DateTime.DaysInMonth(current.Year, current.Month))
|
||||
current = current.AddDays(officialDay - current.Day);
|
||||
}
|
||||
else
|
||||
{
|
||||
months++;
|
||||
}
|
||||
|
||||
break;
|
||||
case Phase.Days:
|
||||
if (current.AddDays(days + 1) > date2)
|
||||
{
|
||||
current = current.AddDays(days);
|
||||
var timespan = date2 - current;
|
||||
span = new DateTimeSpan(
|
||||
years,
|
||||
months,
|
||||
days,
|
||||
timespan.Hours,
|
||||
timespan.Minutes,
|
||||
timespan.Seconds,
|
||||
timespan.Milliseconds);
|
||||
phase = Phase.Done;
|
||||
}
|
||||
else
|
||||
{
|
||||
days++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return span;
|
||||
}
|
||||
|
||||
private enum Phase
|
||||
{
|
||||
Years,
|
||||
Months,
|
||||
Days,
|
||||
Done,
|
||||
}
|
||||
}
|
||||
}
|
132
Unosquare.Swan.Lite/Definitions.Types.cs
Normal file
132
Unosquare.Swan.Lite/Definitions.Types.cs
Normal file
@ -0,0 +1,132 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful constants and definitions.
|
||||
/// </summary>
|
||||
public static partial class Definitions
|
||||
{
|
||||
#region Main Dictionary Definition
|
||||
|
||||
/// <summary>
|
||||
/// The basic types information.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<Type, ExtendedTypeInfo> BasicTypesInfo =
|
||||
new Dictionary<Type, ExtendedTypeInfo>
|
||||
{
|
||||
// Non-Nullables
|
||||
{typeof(DateTime), new ExtendedTypeInfo<DateTime>()},
|
||||
{typeof(byte), new ExtendedTypeInfo<byte>()},
|
||||
{typeof(sbyte), new ExtendedTypeInfo<sbyte>()},
|
||||
{typeof(int), new ExtendedTypeInfo<int>()},
|
||||
{typeof(uint), new ExtendedTypeInfo<uint>()},
|
||||
{typeof(short), new ExtendedTypeInfo<short>()},
|
||||
{typeof(ushort), new ExtendedTypeInfo<ushort>()},
|
||||
{typeof(long), new ExtendedTypeInfo<long>()},
|
||||
{typeof(ulong), new ExtendedTypeInfo<ulong>()},
|
||||
{typeof(float), new ExtendedTypeInfo<float>()},
|
||||
{typeof(double), new ExtendedTypeInfo<double>()},
|
||||
{typeof(char), new ExtendedTypeInfo<char>()},
|
||||
{typeof(bool), new ExtendedTypeInfo<bool>()},
|
||||
{typeof(decimal), new ExtendedTypeInfo<decimal>()},
|
||||
{typeof(Guid), new ExtendedTypeInfo<Guid>()},
|
||||
|
||||
// Strings is also considered a basic type (it's the only basic reference type)
|
||||
{typeof(string), new ExtendedTypeInfo<string>()},
|
||||
|
||||
// Nullables
|
||||
{typeof(DateTime?), new ExtendedTypeInfo<DateTime?>()},
|
||||
{typeof(byte?), new ExtendedTypeInfo<byte?>()},
|
||||
{typeof(sbyte?), new ExtendedTypeInfo<sbyte?>()},
|
||||
{typeof(int?), new ExtendedTypeInfo<int?>()},
|
||||
{typeof(uint?), new ExtendedTypeInfo<uint?>()},
|
||||
{typeof(short?), new ExtendedTypeInfo<short?>()},
|
||||
{typeof(ushort?), new ExtendedTypeInfo<ushort?>()},
|
||||
{typeof(long?), new ExtendedTypeInfo<long?>()},
|
||||
{typeof(ulong?), new ExtendedTypeInfo<ulong?>()},
|
||||
{typeof(float?), new ExtendedTypeInfo<float?>()},
|
||||
{typeof(double?), new ExtendedTypeInfo<double?>()},
|
||||
{typeof(char?), new ExtendedTypeInfo<char?>()},
|
||||
{typeof(bool?), new ExtendedTypeInfo<bool?>()},
|
||||
{typeof(decimal?), new ExtendedTypeInfo<decimal?>()},
|
||||
{typeof(Guid?), new ExtendedTypeInfo<Guid?>()},
|
||||
|
||||
// Additional Types
|
||||
{typeof(TimeSpan), new ExtendedTypeInfo<TimeSpan>()},
|
||||
{typeof(TimeSpan?), new ExtendedTypeInfo<TimeSpan?>()},
|
||||
{typeof(IPAddress), new ExtendedTypeInfo<IPAddress>()},
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Contains all basic types, including string, date time, and all of their nullable counterparts.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All basic types.
|
||||
/// </value>
|
||||
public static List<Type> AllBasicTypes { get; } = new List<Type>(BasicTypesInfo.Keys.ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Gets all numeric types including their nullable counterparts.
|
||||
/// Note that Booleans and Guids are not considered numeric types.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All numeric types.
|
||||
/// </value>
|
||||
public static List<Type> AllNumericTypes { get; } = new List<Type>(
|
||||
BasicTypesInfo
|
||||
.Where(kvp => kvp.Value.IsNumeric)
|
||||
.Select(kvp => kvp.Key).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Gets all numeric types without their nullable counterparts.
|
||||
/// Note that Booleans and Guids are not considered numeric types.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All numeric value types.
|
||||
/// </value>
|
||||
public static List<Type> AllNumericValueTypes { get; } = new List<Type>(
|
||||
BasicTypesInfo
|
||||
.Where(kvp => kvp.Value.IsNumeric && kvp.Value.IsNullableValueType == false)
|
||||
.Select(kvp => kvp.Key).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Contains all basic value types. i.e. excludes string and nullables.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All basic value types.
|
||||
/// </value>
|
||||
public static List<Type> AllBasicValueTypes { get; } = new List<Type>(
|
||||
BasicTypesInfo
|
||||
.Where(kvp => kvp.Value.IsValueType)
|
||||
.Select(kvp => kvp.Key).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Contains all basic value types including the string type. i.e. excludes nullables.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All basic value and string types.
|
||||
/// </value>
|
||||
public static List<Type> AllBasicValueAndStringTypes { get; } = new List<Type>(
|
||||
BasicTypesInfo
|
||||
.Where(kvp => kvp.Value.IsValueType || kvp.Key == typeof(string))
|
||||
.Select(kvp => kvp.Key).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Gets all nullable value types. i.e. excludes string and all basic value types.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// All basic nullable value types.
|
||||
/// </value>
|
||||
public static List<Type> AllBasicNullableValueTypes { get; } = new List<Type>(
|
||||
BasicTypesInfo
|
||||
.Where(kvp => kvp.Value.IsNullableValueType)
|
||||
.Select(kvp => kvp.Key).ToArray());
|
||||
}
|
||||
}
|
39
Unosquare.Swan.Lite/Definitions.cs
Normal file
39
Unosquare.Swan.Lite/Definitions.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Contains useful constants and definitions.
|
||||
/// </summary>
|
||||
public static partial class Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// The MS Windows codepage 1252 encoding used in some legacy scenarios
|
||||
/// such as default CSV text encoding from Excel.
|
||||
/// </summary>
|
||||
public static readonly Encoding Windows1252Encoding;
|
||||
|
||||
/// <summary>
|
||||
/// The encoding associated with the default ANSI code page in the operating
|
||||
/// system's regional and language settings.
|
||||
/// </summary>
|
||||
public static readonly Encoding CurrentAnsiEncoding;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the <see cref="Definitions"/> class.
|
||||
/// </summary>
|
||||
static Definitions()
|
||||
{
|
||||
CurrentAnsiEncoding = Encoding.GetEncoding(default(int));
|
||||
try
|
||||
{
|
||||
Windows1252Encoding = Encoding.GetEncoding(1252);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore, the codepage is not available use default
|
||||
Windows1252Encoding = CurrentAnsiEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
Unosquare.Swan.Lite/Enums.cs
Normal file
60
Unosquare.Swan.Lite/Enums.cs
Normal file
@ -0,0 +1,60 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of Operating Systems.
|
||||
/// </summary>
|
||||
public enum OperatingSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown OS
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Windows
|
||||
/// </summary>
|
||||
Windows,
|
||||
|
||||
/// <summary>
|
||||
/// UNIX/Linux
|
||||
/// </summary>
|
||||
Unix,
|
||||
|
||||
/// <summary>
|
||||
/// macOS (OSX)
|
||||
/// </summary>
|
||||
Osx,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates the different Application Worker States.
|
||||
/// </summary>
|
||||
public enum AppWorkerState
|
||||
{
|
||||
/// <summary>
|
||||
/// The stopped
|
||||
/// </summary>
|
||||
Stopped,
|
||||
|
||||
/// <summary>
|
||||
/// The running
|
||||
/// </summary>
|
||||
Running,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines Endianness, big or little.
|
||||
/// </summary>
|
||||
public enum Endianness
|
||||
{
|
||||
/// <summary>
|
||||
/// In big endian, you store the most significant byte in the smallest address.
|
||||
/// </summary>
|
||||
Big,
|
||||
|
||||
/// <summary>
|
||||
/// In little endian, you store the least significant byte in the smallest address.
|
||||
/// </summary>
|
||||
Little,
|
||||
}
|
||||
}
|
168
Unosquare.Swan.Lite/Eventing.cs
Normal file
168
Unosquare.Swan.Lite/Eventing.cs
Normal file
@ -0,0 +1,168 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments representing the message that is logged
|
||||
/// on to the terminal. Use the properties to forward the data to
|
||||
/// your logger of choice.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.EventArgs" />
|
||||
public class LogMessageReceivedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogMessageReceivedEventArgs" /> class.
|
||||
/// </summary>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <param name="messageType">Type of the message.</param>
|
||||
/// <param name="utcDate">The UTC date.</param>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="extendedData">The extended data.</param>
|
||||
/// <param name="callerMemberName">Name of the caller member.</param>
|
||||
/// <param name="callerFilePath">The caller file path.</param>
|
||||
/// <param name="callerLineNumber">The caller line number.</param>
|
||||
public LogMessageReceivedEventArgs(
|
||||
ulong sequence,
|
||||
LogMessageType messageType,
|
||||
DateTime utcDate,
|
||||
string source,
|
||||
string message,
|
||||
object extendedData,
|
||||
string callerMemberName,
|
||||
string callerFilePath,
|
||||
int callerLineNumber)
|
||||
{
|
||||
Sequence = sequence;
|
||||
MessageType = messageType;
|
||||
UtcDate = utcDate;
|
||||
Source = source;
|
||||
Message = message;
|
||||
CallerMemberName = callerMemberName;
|
||||
CallerFilePath = callerFilePath;
|
||||
CallerLineNumber = callerLineNumber;
|
||||
ExtendedData = extendedData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets logging message sequence.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The sequence.
|
||||
/// </value>
|
||||
public ulong Sequence { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the message.
|
||||
/// It can be a combination as the enumeration is a set of bitwise flags.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The type of the message.
|
||||
/// </value>
|
||||
public LogMessageType MessageType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the UTC date at which the event at which the message was logged.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The UTC date.
|
||||
/// </value>
|
||||
public DateTime UtcDate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the source where the logging message
|
||||
/// came from. This can come empty if the logger did not set it.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The source.
|
||||
/// </value>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the body of the message.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The message.
|
||||
/// </value>
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the caller member.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the caller member.
|
||||
/// </value>
|
||||
public string CallerMemberName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caller file path.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The caller file path.
|
||||
/// </value>
|
||||
public string CallerFilePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the caller line number.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The caller line number.
|
||||
/// </value>
|
||||
public int CallerLineNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an object representing extended data.
|
||||
/// It could be an exception or anything else.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The extended data.
|
||||
/// </value>
|
||||
public object ExtendedData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Extended Data properties cast as an Exception (if possible)
|
||||
/// Otherwise, it return null.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The exception.
|
||||
/// </value>
|
||||
public Exception Exception => ExtendedData as Exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments representing a message logged and about to be
|
||||
/// displayed on the terminal (console). Set the CancelOutput property in the
|
||||
/// event handler to prevent the terminal from displaying the message.
|
||||
/// </summary>
|
||||
/// <seealso cref="LogMessageReceivedEventArgs" />
|
||||
public class LogMessageDisplayingEventArgs : LogMessageReceivedEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogMessageDisplayingEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="data">The <see cref="LogMessageReceivedEventArgs"/> instance containing the event data.</param>
|
||||
public LogMessageDisplayingEventArgs(LogMessageReceivedEventArgs data)
|
||||
: base(
|
||||
data.Sequence,
|
||||
data.MessageType,
|
||||
data.UtcDate,
|
||||
data.Source,
|
||||
data.Message,
|
||||
data.ExtendedData,
|
||||
data.CallerMemberName,
|
||||
data.CallerFilePath,
|
||||
data.CallerLineNumber)
|
||||
{
|
||||
CancelOutput = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the displaying of the
|
||||
/// logging message should be canceled.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [cancel output]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool CancelOutput { get; set; }
|
||||
}
|
||||
}
|
672
Unosquare.Swan.Lite/Extensions.ByteArrays.cs
Normal file
672
Unosquare.Swan.Lite/Extensions.ByteArrays.cs
Normal file
@ -0,0 +1,672 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Provides various extension methods for byte arrays and streams.
|
||||
/// </summary>
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to its lower-case, hexadecimal representation.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <param name="addPrefix">if set to <c>true</c> add the 0x prefix tot he output.</param>
|
||||
/// <returns>
|
||||
/// The specified string instance; no actual conversion is performed.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">bytes.</exception>
|
||||
public static string ToLowerHex(this byte[] bytes, bool addPrefix = false)
|
||||
=> ToHex(bytes, addPrefix, "x2");
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to its upper-case, hexadecimal representation.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <param name="addPrefix">if set to <c>true</c> [add prefix].</param>
|
||||
/// <returns>
|
||||
/// The specified string instance; no actual conversion is performed.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">bytes.</exception>
|
||||
public static string ToUpperHex(this byte[] bytes, bool addPrefix = false)
|
||||
=> ToHex(bytes, addPrefix, "X2");
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to a sequence of dash-separated, hexadecimal,
|
||||
/// uppercase characters.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <returns>
|
||||
/// A string of hexadecimal pairs separated by hyphens, where each pair represents
|
||||
/// the corresponding element in value; for example, "7F-2C-4A-00".
|
||||
/// </returns>
|
||||
public static string ToDashedHex(this byte[] bytes) => BitConverter.ToString(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes to a base-64 encoded string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes.</param>
|
||||
/// <returns>A <see cref="System.String" /> converted from an array of bytes.</returns>
|
||||
public static string ToBase64(this byte[] bytes) => Convert.ToBase64String(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a set of hexadecimal characters (uppercase or lowercase)
|
||||
/// to a byte array. String length must be a multiple of 2 and
|
||||
/// any prefix (such as 0x) has to be avoided for this to work properly.
|
||||
/// </summary>
|
||||
/// <param name="hex">The hexadecimal.</param>
|
||||
/// <returns>
|
||||
/// A byte array containing the results of encoding the specified set of characters.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">hex.</exception>
|
||||
public static byte[] ConvertHexadecimalToBytes(this string hex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(hex))
|
||||
throw new ArgumentNullException(nameof(hex));
|
||||
|
||||
return Enumerable
|
||||
.Range(0, hex.Length / 2)
|
||||
.Select(x => Convert.ToByte(hex.Substring(x * 2, 2), 16))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit value at the given offset.
|
||||
/// </summary>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <returns>
|
||||
/// Bit value at the given offset.
|
||||
/// </returns>
|
||||
public static byte GetBitValueAt(this byte b, byte offset, byte length = 1) => (byte)((b >> offset) & ~(0xff << length));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bit value at the given offset.
|
||||
/// </summary>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Bit value at the given offset.</returns>
|
||||
public static byte SetBitValueAt(this byte b, byte offset, byte length, byte value)
|
||||
{
|
||||
var mask = ~(0xff << length);
|
||||
var valueAt = (byte)(value & mask);
|
||||
|
||||
return (byte)((valueAt << offset) | (b & ~(mask << offset)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bit value at the given offset.
|
||||
/// </summary>
|
||||
/// <param name="b">The b.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>Bit value at the given offset.</returns>
|
||||
public static byte SetBitValueAt(this byte b, byte offset, byte value) => b.SetBitValueAt(offset, 1, value);
|
||||
|
||||
/// <summary>
|
||||
/// Splits a byte array delimited by the specified sequence of bytes.
|
||||
/// Each individual element in the result will contain the split sequence terminator if it is found to be delimited by it.
|
||||
/// For example if you split [1,2,3,4] by a sequence of [2,3] this method will return a list with 2 byte arrays, one containing [1,2,3] and the
|
||||
/// second one containing 4. Use the Trim extension methods to remove terminator sequences.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="offset">The offset at which to start splitting bytes. Any bytes before this will be discarded.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// A byte array containing the results the specified sequence of bytes.
|
||||
/// </returns>
|
||||
/// <exception cref="System.ArgumentNullException">
|
||||
/// buffer
|
||||
/// or
|
||||
/// sequence.
|
||||
/// </exception>
|
||||
public static List<byte[]> Split(this byte[] buffer, int offset, params byte[] sequence)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (sequence == null)
|
||||
throw new ArgumentNullException(nameof(sequence));
|
||||
|
||||
var seqOffset = offset.Clamp(0, buffer.Length - 1);
|
||||
|
||||
var result = new List<byte[]>();
|
||||
|
||||
while (seqOffset < buffer.Length)
|
||||
{
|
||||
var separatorStartIndex = buffer.GetIndexOf(sequence, seqOffset);
|
||||
|
||||
if (separatorStartIndex >= 0)
|
||||
{
|
||||
var item = new byte[separatorStartIndex - seqOffset + sequence.Length];
|
||||
Array.Copy(buffer, seqOffset, item, 0, item.Length);
|
||||
result.Add(item);
|
||||
seqOffset += item.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var item = new byte[buffer.Length - seqOffset];
|
||||
Array.Copy(buffer, seqOffset, item, 0, item.Length);
|
||||
result.Add(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones the specified buffer, byte by byte.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
|
||||
public static byte[] DeepClone(this byte[] buffer)
|
||||
{
|
||||
if (buffer == null)
|
||||
return null;
|
||||
|
||||
var result = new byte[buffer.Length];
|
||||
Array.Copy(buffer, result, buffer.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified sequence from the start of the buffer if the buffer begins with such sequence.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// A new trimmed byte array.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffer.</exception>
|
||||
public static byte[] TrimStart(this byte[] buffer, params byte[] sequence)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (buffer.StartsWith(sequence) == false)
|
||||
return buffer.DeepClone();
|
||||
|
||||
var result = new byte[buffer.Length - sequence.Length];
|
||||
Array.Copy(buffer, sequence.Length, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified sequence from the end of the buffer if the buffer ends with such sequence.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// A byte array containing the results of encoding the specified set of characters.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffer.</exception>
|
||||
public static byte[] TrimEnd(this byte[] buffer, params byte[] sequence)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
if (buffer.EndsWith(sequence) == false)
|
||||
return buffer.DeepClone();
|
||||
|
||||
var result = new byte[buffer.Length - sequence.Length];
|
||||
Array.Copy(buffer, 0, result, 0, result.Length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the specified sequence from the end and the start of the buffer
|
||||
/// if the buffer ends and/or starts with such sequence.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
|
||||
public static byte[] Trim(this byte[] buffer, params byte[] sequence)
|
||||
{
|
||||
var trimStart = buffer.TrimStart(sequence);
|
||||
return trimStart.TrimEnd(sequence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified buffer ends with the given sequence of bytes.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// True if the specified buffer is ends; otherwise, false.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffer.</exception>
|
||||
public static bool EndsWith(this byte[] buffer, params byte[] sequence)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
var startIndex = buffer.Length - sequence.Length;
|
||||
return buffer.GetIndexOf(sequence, startIndex) == startIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the specified buffer starts with the given sequence of bytes.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns><c>true</c> if the specified buffer starts; otherwise, <c>false</c>.</returns>
|
||||
public static bool StartsWith(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the buffer contains the specified sequence.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [contains] [the specified sequence]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool Contains(this byte[] buffer, params byte[] sequence) => buffer.GetIndexOf(sequence) >= 0;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the buffer exactly matches, byte by byte the specified sequence.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is equal to] [the specified sequence]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffer.</exception>
|
||||
public static bool IsEqualTo(this byte[] buffer, params byte[] sequence)
|
||||
{
|
||||
if (ReferenceEquals(buffer, sequence))
|
||||
return true;
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
return buffer.Length == sequence.Length && buffer.GetIndexOf(sequence) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first instance of the matched sequence based on the given offset.
|
||||
/// If nomatches are found then this method returns -1.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="sequence">The sequence.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <returns>The index of the sequence.</returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// buffer
|
||||
/// or
|
||||
/// sequence.
|
||||
/// </exception>
|
||||
public static int GetIndexOf(this byte[] buffer, byte[] sequence, int offset = 0)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
if (sequence == null)
|
||||
throw new ArgumentNullException(nameof(sequence));
|
||||
if (sequence.Length == 0)
|
||||
return -1;
|
||||
if (sequence.Length > buffer.Length)
|
||||
return -1;
|
||||
|
||||
var seqOffset = offset < 0 ? 0 : offset;
|
||||
|
||||
var matchedCount = 0;
|
||||
for (var i = seqOffset; i < buffer.Length; i++)
|
||||
{
|
||||
if (buffer[i] == sequence[matchedCount])
|
||||
matchedCount++;
|
||||
else
|
||||
matchedCount = 0;
|
||||
|
||||
if (matchedCount == sequence.Length)
|
||||
return i - (matchedCount - 1);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the Memory Stream with the specified buffer.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>
|
||||
/// The same MemoryStream instance.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// stream
|
||||
/// or
|
||||
/// buffer.
|
||||
/// </exception>
|
||||
public static MemoryStream Append(this MemoryStream stream, byte[] buffer)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException(nameof(buffer));
|
||||
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Appends the Memory Stream with the specified buffer.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>
|
||||
/// Block of bytes to the current stream using data read from a buffer.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffer.</exception>
|
||||
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte> buffer) => Append(stream, buffer?.ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Appends the Memory Stream with the specified set of buffers.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="buffers">The buffers.</param>
|
||||
/// <returns>
|
||||
/// Block of bytes to the current stream using data read from a buffer.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">buffers.</exception>
|
||||
public static MemoryStream Append(this MemoryStream stream, IEnumerable<byte[]> buffers)
|
||||
{
|
||||
if (buffers == null)
|
||||
throw new ArgumentNullException(nameof(buffers));
|
||||
|
||||
foreach (var buffer in buffers)
|
||||
Append(stream, buffer);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes into text with the specified encoding.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
|
||||
public static string ToText(this IEnumerable<byte> buffer, Encoding encoding) => encoding.GetString(buffer.ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes into text with UTF8 encoding.
|
||||
/// </summary>
|
||||
/// <param name="buffer">The buffer.</param>
|
||||
/// <returns>A <see cref="System.String" /> that contains the results of decoding the specified sequence of bytes.</returns>
|
||||
public static string ToText(this IEnumerable<byte> buffer) => buffer.ToText(Encoding.UTF8);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a sub-array from the specified <paramref name="array"/>. A sub-array starts at
|
||||
/// the specified element position in <paramref name="array"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of T that receives a sub-array, or an empty array of T if any problems with
|
||||
/// the parameters.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T from which to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <param name="startIndex">
|
||||
/// An <see cref="int"/> that represents the zero-based starting position of
|
||||
/// a sub-array in <paramref name="array"/>.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// An <see cref="int"/> that represents the number of elements to retrieve.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
public static T[] SubArray<T>(this T[] array, int startIndex, int length)
|
||||
{
|
||||
int len;
|
||||
if (array == null || (len = array.Length) == 0)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex < 0 || length <= 0 || startIndex + length > len)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex == 0 && length == len)
|
||||
return array;
|
||||
|
||||
var subArray = new T[length];
|
||||
Array.Copy(array, startIndex, subArray, 0, length);
|
||||
|
||||
return subArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a sub-array from the specified <paramref name="array"/>. A sub-array starts at
|
||||
/// the specified element position in <paramref name="array"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of T that receives a sub-array, or an empty array of T if any problems with
|
||||
/// the parameters.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T from which to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <param name="startIndex">
|
||||
/// A <see cref="long"/> that represents the zero-based starting position of
|
||||
/// a sub-array in <paramref name="array"/>.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// A <see cref="long"/> that represents the number of elements to retrieve.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
public static T[] SubArray<T>(this T[] array, long startIndex, long length) => array.SubArray((int)startIndex, (int)length);
|
||||
|
||||
/// <summary>
|
||||
/// Reads the bytes asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="bufferLength">Length of the buffer.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A byte array containing the results of encoding the specified set of characters.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">stream.</exception>
|
||||
public static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength, CancellationToken ct = default)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
|
||||
using (var dest = new MemoryStream())
|
||||
{
|
||||
try
|
||||
{
|
||||
var buff = new byte[bufferLength];
|
||||
while (length > 0)
|
||||
{
|
||||
if (length < bufferLength)
|
||||
bufferLength = (int)length;
|
||||
|
||||
var nread = await stream.ReadAsync(buff, 0, bufferLength, ct).ConfigureAwait(false);
|
||||
if (nread == 0)
|
||||
break;
|
||||
|
||||
dest.Write(buff, 0, nread);
|
||||
length -= nread;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return dest.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the bytes asynchronous.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
/// <returns>
|
||||
/// A byte array containing the results of encoding the specified set of characters.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">stream.</exception>
|
||||
public static async Task<byte[]> ReadBytesAsync(this Stream stream, int length, CancellationToken ct = default)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
|
||||
var buff = new byte[length];
|
||||
var offset = 0;
|
||||
try
|
||||
{
|
||||
while (length > 0)
|
||||
{
|
||||
var nread = await stream.ReadAsync(buff, offset, length, ct).ConfigureAwait(false);
|
||||
if (nread == 0)
|
||||
break;
|
||||
|
||||
offset += nread;
|
||||
length -= nread;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return buff.SubArray(0, offset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of sbytes to an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="sbyteArray">The sbyte array.</param>
|
||||
/// <returns>
|
||||
/// The byte array from conversion.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sbyteArray.</exception>
|
||||
public static byte[] ToByteArray(this sbyte[] sbyteArray)
|
||||
{
|
||||
if (sbyteArray == null)
|
||||
throw new ArgumentNullException(nameof(sbyteArray));
|
||||
|
||||
var byteArray = new byte[sbyteArray.Length];
|
||||
for (var index = 0; index < sbyteArray.Length; index++)
|
||||
byteArray[index] = (byte)sbyteArray[index];
|
||||
|
||||
return byteArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives a byte array and returns it transformed in an sbyte array.
|
||||
/// </summary>
|
||||
/// <param name="byteArray">The byte array.</param>
|
||||
/// <returns>
|
||||
/// The sbyte array from conversion.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">byteArray.</exception>
|
||||
public static sbyte[] ToSByteArray(this byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null)
|
||||
throw new ArgumentNullException(nameof(byteArray));
|
||||
|
||||
var sbyteArray = new sbyte[byteArray.Length];
|
||||
for (var index = 0; index < byteArray.Length; index++)
|
||||
sbyteArray[index] = (sbyte)byteArray[index];
|
||||
return sbyteArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sbytes from a string.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="s">The s.</param>
|
||||
/// <returns>The sbyte array from string.</returns>
|
||||
public static sbyte[] GetSBytes(this Encoding encoding, string s)
|
||||
=> encoding.GetBytes(s).ToSByteArray();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string from a sbyte array.
|
||||
/// </summary>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>The string.</returns>
|
||||
public static string GetString(this Encoding encoding, sbyte[] data)
|
||||
=> encoding.GetString(data.ToByteArray());
|
||||
|
||||
/// <summary>
|
||||
/// Reads a number of characters from the current source Stream and writes the data to the target array at the
|
||||
/// specified index.
|
||||
/// </summary>
|
||||
/// <param name="sourceStream">The source stream.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="start">The start.</param>
|
||||
/// <param name="count">The count.</param>
|
||||
/// <returns>
|
||||
/// The number of bytes read.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// sourceStream
|
||||
/// or
|
||||
/// target.
|
||||
/// </exception>
|
||||
public static int ReadInput(this Stream sourceStream, ref sbyte[] target, int start, int count)
|
||||
{
|
||||
if (sourceStream == null)
|
||||
throw new ArgumentNullException(nameof(sourceStream));
|
||||
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
// Returns 0 bytes if not enough space in target
|
||||
if (target.Length == 0)
|
||||
return 0;
|
||||
|
||||
var receiver = new byte[target.Length];
|
||||
var bytesRead = 0;
|
||||
var startIndex = start;
|
||||
var bytesToRead = count;
|
||||
while (bytesToRead > 0)
|
||||
{
|
||||
var n = sourceStream.Read(receiver, startIndex, bytesToRead);
|
||||
if (n == 0)
|
||||
break;
|
||||
bytesRead += n;
|
||||
startIndex += n;
|
||||
bytesToRead -= n;
|
||||
}
|
||||
|
||||
// Returns -1 if EOF
|
||||
if (bytesRead == 0)
|
||||
return -1;
|
||||
|
||||
for (var i = start; i < start + bytesRead; i++)
|
||||
target[i] = (sbyte)receiver[i];
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private static string ToHex(byte[] bytes, bool addPrefix, string format)
|
||||
{
|
||||
if (bytes == null)
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
|
||||
var sb = new StringBuilder(bytes.Length * 2);
|
||||
|
||||
foreach (var item in bytes)
|
||||
sb.Append(item.ToString(format, CultureInfo.InvariantCulture));
|
||||
|
||||
return $"{(addPrefix ? "0x" : string.Empty)}{sb}";
|
||||
}
|
||||
}
|
||||
}
|
230
Unosquare.Swan.Lite/Extensions.Dates.cs
Normal file
230
Unosquare.Swan.Lite/Extensions.Dates.cs
Normal file
@ -0,0 +1,230 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Provides various extension methods for dates.
|
||||
/// </summary>
|
||||
public static class DateExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, int> DateRanges = new Dictionary<string, int>()
|
||||
{
|
||||
{ "minute", 59},
|
||||
{ "hour", 23},
|
||||
{ "dayOfMonth", 31},
|
||||
{ "month", 12},
|
||||
{ "dayOfWeek", 6},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the date to a YYYY-MM-DD string.
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <returns>The concatenation of date.Year, date.Month and date.Day.</returns>
|
||||
public static string ToSortableDate(this DateTime date)
|
||||
=> $"{date.Year:0000}-{date.Month:00}-{date.Day:00}";
|
||||
|
||||
/// <summary>
|
||||
/// Converts the date to a YYYY-MM-DD HH:II:SS string.
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <returns>The concatenation of date.Year, date.Month, date.Day, date.Hour, date.Minute and date.Second.</returns>
|
||||
public static string ToSortableDateTime(this DateTime date)
|
||||
=> $"{date.Year:0000}-{date.Month:00}-{date.Day:00} {date.Hour:00}:{date.Minute:00}:{date.Second:00}";
|
||||
|
||||
/// <summary>
|
||||
/// Parses a YYYY-MM-DD and optionally it time part, HH:II:SS into a DateTime.
|
||||
/// </summary>
|
||||
/// <param name="sortableDate">The sortable date.</param>
|
||||
/// <returns>
|
||||
/// A new instance of the DateTime structure to
|
||||
/// the specified year, month, day, hour, minute and second.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sortableDate.</exception>
|
||||
/// <exception cref="Exception">
|
||||
/// Represents errors that occur during application execution.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// Unable to parse sortable date and time. - sortableDate.
|
||||
/// </exception>
|
||||
public static DateTime ToDateTime(this string sortableDate)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sortableDate))
|
||||
throw new ArgumentNullException(nameof(sortableDate));
|
||||
|
||||
var hour = 0;
|
||||
var minute = 0;
|
||||
var second = 0;
|
||||
|
||||
var dateTimeParts = sortableDate.Split(' ');
|
||||
|
||||
try
|
||||
{
|
||||
if (dateTimeParts.Length != 1 && dateTimeParts.Length != 2)
|
||||
throw new Exception();
|
||||
|
||||
var dateParts = dateTimeParts[0].Split('-');
|
||||
if (dateParts.Length != 3) throw new Exception();
|
||||
|
||||
var year = int.Parse(dateParts[0]);
|
||||
var month = int.Parse(dateParts[1]);
|
||||
var day = int.Parse(dateParts[2]);
|
||||
|
||||
if (dateTimeParts.Length > 1)
|
||||
{
|
||||
var timeParts = dateTimeParts[1].Split(':');
|
||||
if (timeParts.Length != 3) throw new Exception();
|
||||
|
||||
hour = int.Parse(timeParts[0]);
|
||||
minute = int.Parse(timeParts[1]);
|
||||
second = int.Parse(timeParts[2]);
|
||||
}
|
||||
|
||||
return new DateTime(year, month, day, hour, minute, second);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new ArgumentException("Unable to parse sortable date and time.", nameof(sortableDate));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a date's range.
|
||||
/// </summary>
|
||||
/// <param name="startDate">The start date.</param>
|
||||
/// <param name="endDate">The end date.</param>
|
||||
/// <returns>
|
||||
/// A sequence of integral numbers within a specified date's range.
|
||||
/// </returns>
|
||||
public static IEnumerable<DateTime> DateRange(this DateTime startDate, DateTime endDate)
|
||||
=> Enumerable.Range(0, (endDate - startDate).Days + 1).Select(d => startDate.AddDays(d));
|
||||
|
||||
/// <summary>
|
||||
/// Rounds up a date to match a timespan.
|
||||
/// </summary>
|
||||
/// <param name="date">The datetime.</param>
|
||||
/// <param name="timeSpan">The timespan to match.</param>
|
||||
/// <returns>
|
||||
/// A new instance of the DateTime structure to the specified datetime and timespan ticks.
|
||||
/// </returns>
|
||||
public static DateTime RoundUp(this DateTime date, TimeSpan timeSpan)
|
||||
=> new DateTime(((date.Ticks + timeSpan.Ticks - 1) / timeSpan.Ticks) * timeSpan.Ticks);
|
||||
|
||||
/// <summary>
|
||||
/// Get this datetime as a Unix epoch timestamp (seconds since Jan 1, 1970, midnight UTC).
|
||||
/// </summary>
|
||||
/// <param name="date">The date to convert.</param>
|
||||
/// <returns>Seconds since Unix epoch.</returns>
|
||||
public static long ToUnixEpochDate(this DateTime date)
|
||||
{
|
||||
#if NETSTANDARD2_0
|
||||
return new DateTimeOffset(date).ToUniversalTime().ToUnixTimeSeconds();
|
||||
#else
|
||||
var epochTicks = new DateTime(1970, 1, 1).Ticks;
|
||||
|
||||
return (date.Ticks - epochTicks) / TimeSpan.TicksPerSecond;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a Date to another and returns a <c>DateTimeSpan</c>.
|
||||
/// </summary>
|
||||
/// <param name="dateStart">The date start.</param>
|
||||
/// <param name="dateEnd">The date end.</param>
|
||||
/// <returns>A DateTimeSpan with the Years, Months, Days, Hours, Minutes, Seconds and Milliseconds between the dates.</returns>
|
||||
public static DateTimeSpan GetDateTimeSpan(this DateTime dateStart, DateTime dateEnd)
|
||||
=> DateTimeSpan.CompareDates(dateStart, dateEnd);
|
||||
|
||||
/// <summary>
|
||||
/// Compare the Date elements(Months, Days, Hours, Minutes).
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <param name="minute">The minute (0-59).</param>
|
||||
/// <param name="hour">The hour. (0-23).</param>
|
||||
/// <param name="dayOfMonth">The day of month. (1-31).</param>
|
||||
/// <param name="month">The month. (1-12).</param>
|
||||
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
|
||||
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
|
||||
public static bool AsCronCanRun(this DateTime date, int? minute = null, int? hour = null, int? dayOfMonth = null, int? month = null, int? dayOfWeek = null)
|
||||
{
|
||||
var results = new List<bool?>
|
||||
{
|
||||
GetElementParts(minute, date.Minute),
|
||||
GetElementParts(hour, date.Hour),
|
||||
GetElementParts(dayOfMonth, date.Day),
|
||||
GetElementParts(month, date.Month),
|
||||
GetElementParts(dayOfWeek, (int) date.DayOfWeek),
|
||||
};
|
||||
|
||||
return results.Any(x => x != false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare the Date elements(Months, Days, Hours, Minutes).
|
||||
/// </summary>
|
||||
/// <param name="date">The date.</param>
|
||||
/// <param name="minute">The minute (0-59).</param>
|
||||
/// <param name="hour">The hour. (0-23).</param>
|
||||
/// <param name="dayOfMonth">The day of month. (1-31).</param>
|
||||
/// <param name="month">The month. (1-12).</param>
|
||||
/// <param name="dayOfWeek">The day of week. (0-6)(Sunday = 0).</param>
|
||||
/// <returns>Returns <c>true</c> if Months, Days, Hours and Minutes match, otherwise <c>false</c>.</returns>
|
||||
public static bool AsCronCanRun(this DateTime date, string minute = "*", string hour = "*", string dayOfMonth = "*", string month = "*", string dayOfWeek = "*")
|
||||
{
|
||||
var results = new List<bool?>
|
||||
{
|
||||
GetElementParts(minute, nameof(minute), date.Minute),
|
||||
GetElementParts(hour, nameof(hour), date.Hour),
|
||||
GetElementParts(dayOfMonth, nameof(dayOfMonth), date.Day),
|
||||
GetElementParts(month, nameof(month), date.Month),
|
||||
GetElementParts(dayOfWeek, nameof(dayOfWeek), (int) date.DayOfWeek),
|
||||
};
|
||||
|
||||
return results.Any(x => x != false);
|
||||
}
|
||||
|
||||
private static bool? GetElementParts(int? status, int value) => status.HasValue ? status.Value == value : (bool?) null;
|
||||
|
||||
private static bool? GetElementParts(string parts, string type, int value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(parts) || parts == "*") return null;
|
||||
|
||||
if (parts.Contains(","))
|
||||
{
|
||||
return parts.Split(',').Select(int.Parse).Contains(value);
|
||||
}
|
||||
|
||||
var stop = DateRanges[type];
|
||||
|
||||
if (parts.Contains("/"))
|
||||
{
|
||||
var multiple = int.Parse(parts.Split('/').Last());
|
||||
var start = type == "dayOfMonth" || type == "month" ? 1 : 0;
|
||||
|
||||
for (var i = start; i <= stop; i += multiple)
|
||||
if (i == value) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parts.Contains("-"))
|
||||
{
|
||||
var range = parts.Split('-');
|
||||
var start = int.Parse(range.First());
|
||||
stop = Math.Max(stop, int.Parse(range.Last()));
|
||||
|
||||
if ((type == "dayOfMonth" || type == "month") && start == 0)
|
||||
start = 1;
|
||||
|
||||
for (var i = start; i <= stop; i++)
|
||||
if (i == value) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return int.Parse(parts) == value;
|
||||
}
|
||||
}
|
||||
}
|
77
Unosquare.Swan.Lite/Extensions.Dictionaries.cs
Normal file
77
Unosquare.Swan.Lite/Extensions.Dictionaries.cs
Normal file
@ -0,0 +1,77 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value if exists or default.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
/// <param name="dict">The dictionary.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <returns>
|
||||
/// The value of the provided key or default.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">dict.</exception>
|
||||
public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
|
||||
{
|
||||
if (dict == null)
|
||||
throw new ArgumentNullException(nameof(dict));
|
||||
|
||||
return dict.ContainsKey(key) ? dict[key] : defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value pair to the Dictionary if the key does not already exist.
|
||||
/// If the value is null, the key will not be updated.
|
||||
///
|
||||
/// Based on <c>ConcurrentDictionary.GetOrAdd</c> method.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
/// <param name="dict">The dictionary.</param>
|
||||
/// <param name="key">The key.</param>
|
||||
/// <param name="valueFactory">The value factory.</param>
|
||||
/// <returns>The value for the key.</returns>
|
||||
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueFactory)
|
||||
{
|
||||
if (dict == null)
|
||||
throw new ArgumentNullException(nameof(dict));
|
||||
|
||||
if (!dict.ContainsKey(key))
|
||||
{
|
||||
var value = valueFactory(key);
|
||||
if (Equals(value, default)) return default;
|
||||
dict[key] = value;
|
||||
}
|
||||
|
||||
return dict[key];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the item action for each element in the Dictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type of the key.</typeparam>
|
||||
/// <typeparam name="TValue">The type of the value.</typeparam>
|
||||
/// <param name="dict">The dictionary.</param>
|
||||
/// <param name="itemAction">The item action.</param>
|
||||
/// <exception cref="ArgumentNullException">dict.</exception>
|
||||
public static void ForEach<TKey, TValue>(this IDictionary<TKey, TValue> dict, Action<TKey, TValue> itemAction)
|
||||
{
|
||||
if (dict == null)
|
||||
throw new ArgumentNullException(nameof(dict));
|
||||
|
||||
foreach (var kvp in dict)
|
||||
{
|
||||
itemAction(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
179
Unosquare.Swan.Lite/Extensions.Functional.cs
Normal file
179
Unosquare.Swan.Lite/Extensions.Functional.cs
Normal file
@ -0,0 +1,179 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Functional programming extension methods.
|
||||
/// </summary>
|
||||
public static class FunctionalExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whens the specified condition.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IQueryable.</typeparam>
|
||||
/// <param name="list">The list.</param>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="fn">The function.</param>
|
||||
/// <returns>
|
||||
/// The IQueryable.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// this
|
||||
/// or
|
||||
/// condition
|
||||
/// or
|
||||
/// fn.
|
||||
/// </exception>
|
||||
public static IQueryable<T> When<T>(
|
||||
this IQueryable<T> list,
|
||||
Func<bool> condition,
|
||||
Func<IQueryable<T>, IQueryable<T>> fn)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
if (condition == null)
|
||||
throw new ArgumentNullException(nameof(condition));
|
||||
|
||||
if (fn == null)
|
||||
throw new ArgumentNullException(nameof(fn));
|
||||
|
||||
return condition() ? fn(list) : list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whens the specified condition.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IEnumerable.</typeparam>
|
||||
/// <param name="list">The list.</param>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="fn">The function.</param>
|
||||
/// <returns>
|
||||
/// The IEnumerable.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// this
|
||||
/// or
|
||||
/// condition
|
||||
/// or
|
||||
/// fn.
|
||||
/// </exception>
|
||||
public static IEnumerable<T> When<T>(
|
||||
this IEnumerable<T> list,
|
||||
Func<bool> condition,
|
||||
Func<IEnumerable<T>, IEnumerable<T>> fn)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
if (condition == null)
|
||||
throw new ArgumentNullException(nameof(condition));
|
||||
|
||||
if (fn == null)
|
||||
throw new ArgumentNullException(nameof(fn));
|
||||
|
||||
return condition() ? fn(list) : list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the value when the condition is true.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IList element.</typeparam>
|
||||
/// <param name="list">The list.</param>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>
|
||||
/// The IList.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// this
|
||||
/// or
|
||||
/// condition
|
||||
/// or
|
||||
/// value.
|
||||
/// </exception>
|
||||
public static IList<T> AddWhen<T>(
|
||||
this IList<T> list,
|
||||
Func<bool> condition,
|
||||
Func<T> value)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
if (condition == null)
|
||||
throw new ArgumentNullException(nameof(condition));
|
||||
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (condition())
|
||||
list.Add(value());
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the value when the condition is true.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IList element.</typeparam>
|
||||
/// <param name="list">The list.</param>
|
||||
/// <param name="condition">if set to <c>true</c> [condition].</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>
|
||||
/// The IList.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">list.</exception>
|
||||
public static IList<T> AddWhen<T>(
|
||||
this IList<T> list,
|
||||
bool condition,
|
||||
T value)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
if (condition)
|
||||
list.Add(value);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the range when the condition is true.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of List element.</typeparam>
|
||||
/// <param name="list">The list.</param>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>
|
||||
/// The List.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// this
|
||||
/// or
|
||||
/// condition
|
||||
/// or
|
||||
/// value.
|
||||
/// </exception>
|
||||
public static List<T> AddRangeWhen<T>(
|
||||
this List<T> list,
|
||||
Func<bool> condition,
|
||||
Func<IEnumerable<T>> value)
|
||||
{
|
||||
if (list == null)
|
||||
throw new ArgumentNullException(nameof(list));
|
||||
|
||||
if (condition == null)
|
||||
throw new ArgumentNullException(nameof(condition));
|
||||
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (condition())
|
||||
list.AddRange(value());
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
469
Unosquare.Swan.Lite/Extensions.Reflection.cs
Normal file
469
Unosquare.Swan.Lite/Extensions.Reflection.cs
Normal file
@ -0,0 +1,469 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Provides various extension methods for Reflection and Types.
|
||||
/// </summary>
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>> CacheGetMethods =
|
||||
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Func<object, object>>(), true);
|
||||
|
||||
private static readonly Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>> CacheSetMethods =
|
||||
new Lazy<ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>>(() => new ConcurrentDictionary<Tuple<bool, PropertyInfo>, Action<object, object[]>>(), true);
|
||||
|
||||
#region Assembly Extensions
|
||||
|
||||
/// <summary>
|
||||
/// Gets all types within an assembly in a safe manner.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly.</param>
|
||||
/// <returns>
|
||||
/// Array of Type objects representing the types specified by an assembly.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">assembly.</exception>
|
||||
public static IEnumerable<Type> GetAllTypes(this Assembly assembly)
|
||||
{
|
||||
if (assembly == null)
|
||||
throw new ArgumentNullException(nameof(assembly));
|
||||
|
||||
try
|
||||
{
|
||||
return assembly.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
return e.Types.Where(t => t != null);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Type Extensions
|
||||
|
||||
/// <summary>
|
||||
/// The closest programmatic equivalent of default(T).
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// Default value of this type.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">type.</exception>
|
||||
public static object GetDefault(this Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
return type.IsValueType() ? Activator.CreateInstance(type) : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this type is compatible with ICollection.
|
||||
/// </summary>
|
||||
/// <param name="sourceType">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified source type is collection; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">sourceType.</exception>
|
||||
public static bool IsCollection(this Type sourceType)
|
||||
{
|
||||
if (sourceType == null)
|
||||
throw new ArgumentNullException(nameof(sourceType));
|
||||
|
||||
return sourceType != typeof(string) &&
|
||||
typeof(IEnumerable).IsAssignableFrom(sourceType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a method from a type given the method name, binding flags, generic types and parameter types.
|
||||
/// </summary>
|
||||
/// <param name="type">Type of the source.</param>
|
||||
/// <param name="bindingFlags">The binding flags.</param>
|
||||
/// <param name="methodName">Name of the method.</param>
|
||||
/// <param name="genericTypes">The generic types.</param>
|
||||
/// <param name="parameterTypes">The parameter types.</param>
|
||||
/// <returns>
|
||||
/// An object that represents the method with the specified name.
|
||||
/// </returns>
|
||||
/// <exception cref="System.Reflection.AmbiguousMatchException">
|
||||
/// The exception that is thrown when binding to a member results in more than one member matching the
|
||||
/// binding criteria. This class cannot be inherited.
|
||||
/// </exception>
|
||||
public static MethodInfo GetMethod(
|
||||
this Type type,
|
||||
BindingFlags bindingFlags,
|
||||
string methodName,
|
||||
Type[] genericTypes,
|
||||
Type[] parameterTypes)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (methodName == null)
|
||||
throw new ArgumentNullException(nameof(methodName));
|
||||
|
||||
if (genericTypes == null)
|
||||
throw new ArgumentNullException(nameof(genericTypes));
|
||||
|
||||
if (parameterTypes == null)
|
||||
throw new ArgumentNullException(nameof(parameterTypes));
|
||||
|
||||
var methods = type
|
||||
.GetMethods(bindingFlags)
|
||||
.Where(mi => string.Equals(methodName, mi.Name, StringComparison.Ordinal))
|
||||
.Where(mi => mi.ContainsGenericParameters)
|
||||
.Where(mi => mi.GetGenericArguments().Length == genericTypes.Length)
|
||||
.Where(mi => mi.GetParameters().Length == parameterTypes.Length)
|
||||
.Select(mi => mi.MakeGenericMethod(genericTypes))
|
||||
.Where(mi => mi.GetParameters().Select(pi => pi.ParameterType).SequenceEqual(parameterTypes))
|
||||
.ToList();
|
||||
|
||||
return methods.Count > 1 ? throw new AmbiguousMatchException() : methods.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance is class.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified type is class; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsClass(this Type type) => type.GetTypeInfo().IsClass;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance is abstract.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified type is abstract; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsAbstract(this Type type) => type.GetTypeInfo().IsAbstract;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance is interface.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified type is interface; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsInterface(this Type type) => type.GetTypeInfo().IsInterface;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this instance is primitive.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified type is primitive; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsPrimitive(this Type type) => type.GetTypeInfo().IsPrimitive;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is value type].
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is value type] [the specified type]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsValueType(this Type type) => type.GetTypeInfo().IsValueType;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is generic type].
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is generic type] [the specified type]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsGenericType(this Type type) => type.GetTypeInfo().IsGenericType;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is generic parameter].
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is generic parameter] [the specified type]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsGenericParameter(this Type type) => type.IsGenericParameter;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified attribute type is defined.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="attributeType">Type of the attribute.</param>
|
||||
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified attribute type is defined; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsDefined(this Type type, Type attributeType, bool inherit) =>
|
||||
type.GetTypeInfo().IsDefined(attributeType, inherit);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the custom attributes.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="attributeType">Type of the attribute.</param>
|
||||
/// <param name="inherit">if set to <c>true</c> [inherit].</param>
|
||||
/// <returns>
|
||||
/// Attributes associated with the property represented by this PropertyInfo object.
|
||||
/// </returns>
|
||||
public static Attribute[] GetCustomAttributes(this Type type, Type attributeType, bool inherit) =>
|
||||
type.GetTypeInfo().GetCustomAttributes(attributeType, inherit).Cast<Attribute>().ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is generic type definition].
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is generic type definition] [the specified type]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsGenericTypeDefinition(this Type type) => type.GetTypeInfo().IsGenericTypeDefinition;
|
||||
|
||||
/// <summary>
|
||||
/// Bases the type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>returns a type of data.</returns>
|
||||
public static Type BaseType(this Type type) => type.GetTypeInfo().BaseType;
|
||||
|
||||
/// <summary>
|
||||
/// Assemblies the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>returns an Assembly object.</returns>
|
||||
public static Assembly Assembly(this Type type) => type.GetTypeInfo().Assembly;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is i enumerable request].
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if [is i enumerable request] [the specified type]; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">type.</exception>
|
||||
public static bool IsIEnumerable(this Type type)
|
||||
=> type == null
|
||||
? throw new ArgumentNullException(nameof(type))
|
||||
: type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(IEnumerable<>);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse using the basic types.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TryParseBasicType(this Type type, object value, out object result)
|
||||
=> TryParseBasicType(type, value.ToStringInvariant(), out result);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to parse using the basic types.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TryParseBasicType(this Type type, string value, out object result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
return Definitions.BasicTypesInfo.ContainsKey(type) && Definitions.BasicTypesInfo[type].TryParse(value, out result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the type of the set basic value to a property.
|
||||
/// </summary>
|
||||
/// <param name="property">The property.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TrySetBasicType(this PropertyInfo property, object value, object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (property.PropertyType.TryParseBasicType(value, out var propertyValue))
|
||||
{
|
||||
property.SetValue(obj, propertyValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the type of the set to an array a basic type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="array">The array.</param>
|
||||
/// <param name="index">The index.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TrySetArrayBasicType(this Type type, object value, Array array, int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
array.SetValue(null, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.TryParseBasicType(value, out var propertyValue))
|
||||
{
|
||||
array.SetValue(propertyValue, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
array.SetValue(null, index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to set a property array with another array.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The property.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if parsing was successful; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TrySetArray(this PropertyInfo propertyInfo, IEnumerable<object> value, object obj)
|
||||
{
|
||||
var elementType = propertyInfo.PropertyType.GetElementType();
|
||||
|
||||
if (elementType == null)
|
||||
return false;
|
||||
|
||||
var targetArray = Array.CreateInstance(elementType, value.Count());
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var sourceElement in value)
|
||||
{
|
||||
var result = elementType.TrySetArrayBasicType(sourceElement, targetArray, i++);
|
||||
|
||||
if (!result) return false;
|
||||
}
|
||||
|
||||
propertyInfo.SetValue(obj, targetArray);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets property actual value or <c>PropertyDisplayAttribute.DefaultValue</c> if presented.
|
||||
///
|
||||
/// If the <c>PropertyDisplayAttribute.Format</c> value is presented, the property value
|
||||
/// will be formatted accordingly.
|
||||
///
|
||||
/// If the object contains a null value, a empty string will be returned.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The property information.</param>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>The property value or null.</returns>
|
||||
public static string ToFormattedString(this PropertyInfo propertyInfo, object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = propertyInfo.GetValue(obj);
|
||||
var attr = Runtime.AttributeCache.RetrieveOne<PropertyDisplayAttribute>(propertyInfo);
|
||||
|
||||
if (attr == null) return value?.ToString() ?? string.Empty;
|
||||
|
||||
var valueToFormat = value ?? attr.DefaultValue;
|
||||
|
||||
return string.IsNullOrEmpty(attr.Format)
|
||||
? (valueToFormat?.ToString() ?? string.Empty)
|
||||
: ConvertObjectAndFormat(propertyInfo.PropertyType, valueToFormat, attr.Format);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a MethodInfo from a Property Get method.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The property information.</param>
|
||||
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
|
||||
/// <returns>
|
||||
/// The cached MethodInfo.
|
||||
/// </returns>
|
||||
public static Func<object, object> GetCacheGetMethod(this PropertyInfo propertyInfo, bool nonPublic = false)
|
||||
{
|
||||
var key = Tuple.Create(!nonPublic, propertyInfo);
|
||||
|
||||
return !nonPublic && !CacheGetMethods.Value.ContainsKey(key) && !propertyInfo.GetGetMethod(true).IsPublic
|
||||
? null
|
||||
: CacheGetMethods.Value
|
||||
.GetOrAdd(key,
|
||||
x => y => x.Item2.GetGetMethod(nonPublic).Invoke(y, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a MethodInfo from a Property Set method.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">The property information.</param>
|
||||
/// <param name="nonPublic">if set to <c>true</c> [non public].</param>
|
||||
/// <returns>
|
||||
/// The cached MethodInfo.
|
||||
/// </returns>
|
||||
public static Action<object, object[]> GetCacheSetMethod(this PropertyInfo propertyInfo, bool nonPublic = false)
|
||||
{
|
||||
var key = Tuple.Create(!nonPublic, propertyInfo);
|
||||
|
||||
return !nonPublic && !CacheSetMethods.Value.ContainsKey(key) && !propertyInfo.GetSetMethod(true).IsPublic
|
||||
? null
|
||||
: CacheSetMethods.Value
|
||||
.GetOrAdd(key,
|
||||
x => (obj, args) => x.Item2.GetSetMethod(nonPublic).Invoke(obj, args));
|
||||
}
|
||||
|
||||
private static string ConvertObjectAndFormat(Type propertyType, object value, string format)
|
||||
{
|
||||
if (propertyType == typeof(DateTime) || propertyType == typeof(DateTime?))
|
||||
return Convert.ToDateTime(value).ToString(format);
|
||||
if (propertyType == typeof(int) || propertyType == typeof(int?))
|
||||
return Convert.ToInt32(value).ToString(format);
|
||||
if (propertyType == typeof(decimal) || propertyType == typeof(decimal?))
|
||||
return Convert.ToDecimal(value).ToString(format);
|
||||
if (propertyType == typeof(double) || propertyType == typeof(double?))
|
||||
return Convert.ToDouble(value).ToString(format);
|
||||
if (propertyType == typeof(byte) || propertyType == typeof(byte?))
|
||||
return Convert.ToByte(value).ToString(format);
|
||||
|
||||
return value?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
520
Unosquare.Swan.Lite/Extensions.Strings.cs
Normal file
520
Unosquare.Swan.Lite/Extensions.Strings.cs
Normal file
@ -0,0 +1,520 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Formatters;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/// <summary>
|
||||
/// String related extension methods.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private const RegexOptions StandardRegexOptions =
|
||||
RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.CultureInvariant;
|
||||
|
||||
private static readonly string[] ByteSuffixes = {"B", "KB", "MB", "GB", "TB"};
|
||||
|
||||
private static readonly Lazy<MD5> Md5Hasher = new Lazy<MD5>(MD5.Create, true);
|
||||
private static readonly Lazy<SHA1> SHA1Hasher = new Lazy<SHA1>(SHA1.Create, true);
|
||||
private static readonly Lazy<SHA256> SHA256Hasher = new Lazy<SHA256>(SHA256.Create, true);
|
||||
private static readonly Lazy<SHA512> SHA512Hasher = new Lazy<SHA512>(SHA512.Create, true);
|
||||
|
||||
private static readonly Lazy<Regex> SplitLinesRegex =
|
||||
new Lazy<Regex>(
|
||||
() => new Regex("\r\n|\r|\n", StandardRegexOptions));
|
||||
|
||||
private static readonly Lazy<Regex> UnderscoreRegex =
|
||||
new Lazy<Regex>(
|
||||
() => new Regex(@"_", StandardRegexOptions));
|
||||
|
||||
private static readonly Lazy<Regex> CamelCaseRegEx =
|
||||
new Lazy<Regex>(
|
||||
() =>
|
||||
new Regex(@"[a-z][A-Z]",
|
||||
StandardRegexOptions));
|
||||
|
||||
private static readonly Lazy<MatchEvaluator> SplitCamelCaseString = new Lazy<MatchEvaluator>(() =>
|
||||
{
|
||||
return m =>
|
||||
{
|
||||
var x = m.ToString();
|
||||
return x[0] + " " + x.Substring(1, x.Length - 1);
|
||||
};
|
||||
});
|
||||
|
||||
private static readonly Lazy<string[]> InvalidFilenameChars =
|
||||
new Lazy<string[]>(() => Path.GetInvalidFileNameChars().Select(c => c.ToString()).ToArray());
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Computes the MD5 hash of the given stream.
|
||||
/// Do not use for large streams as this reads ALL bytes at once.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>
|
||||
/// The computed hash code.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">stream.</exception>
|
||||
public static byte[] ComputeMD5(this Stream stream, bool createHasher = false)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
|
||||
#if NET452
|
||||
var md5 = MD5.Create();
|
||||
const int bufferSize = 4096;
|
||||
|
||||
var readAheadBuffer = new byte[bufferSize];
|
||||
var readAheadBytesRead = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length);
|
||||
|
||||
do
|
||||
{
|
||||
var bytesRead = readAheadBytesRead;
|
||||
var buffer = readAheadBuffer;
|
||||
|
||||
readAheadBuffer = new byte[bufferSize];
|
||||
readAheadBytesRead = stream.Read(readAheadBuffer, 0, readAheadBuffer.Length);
|
||||
|
||||
if (readAheadBytesRead == 0)
|
||||
md5.TransformFinalBlock(buffer, 0, bytesRead);
|
||||
else
|
||||
md5.TransformBlock(buffer, 0, bytesRead, buffer, 0);
|
||||
}
|
||||
while (readAheadBytesRead != 0);
|
||||
|
||||
return md5.Hash;
|
||||
#else
|
||||
using (var ms = new MemoryStream())
|
||||
{
|
||||
stream.Position = 0;
|
||||
stream.CopyTo(ms);
|
||||
|
||||
return (createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(ms.ToArray());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the MD5 hash of the given string using UTF8 byte encoding.
|
||||
/// </summary>
|
||||
/// <param name="value">The input string.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>The computed hash code.</returns>
|
||||
public static byte[] ComputeMD5(this string value, bool createHasher = false) =>
|
||||
Encoding.UTF8.GetBytes(value).ComputeMD5(createHasher);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the MD5 hash of the given byte array.
|
||||
/// </summary>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>The computed hash code.</returns>
|
||||
public static byte[] ComputeMD5(this byte[] data, bool createHasher = false) =>
|
||||
(createHasher ? MD5.Create() : Md5Hasher.Value).ComputeHash(data);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the SHA-1 hash of the given string using UTF8 byte encoding.
|
||||
/// </summary>
|
||||
/// <param name="value">The input string.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>
|
||||
/// The computes a Hash-based Message Authentication Code (HMAC)
|
||||
/// using the SHA1 hash function.
|
||||
/// </returns>
|
||||
public static byte[] ComputeSha1(this string value, bool createHasher = false)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(value);
|
||||
return (createHasher ? SHA1.Create() : SHA1Hasher.Value).ComputeHash(inputBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the SHA-256 hash of the given string using UTF8 byte encoding.
|
||||
/// </summary>
|
||||
/// <param name="value">The input string.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>
|
||||
/// The computes a Hash-based Message Authentication Code (HMAC)
|
||||
/// by using the SHA256 hash function.
|
||||
/// </returns>
|
||||
public static byte[] ComputeSha256(this string value, bool createHasher = false)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(value);
|
||||
return (createHasher ? SHA256.Create() : SHA256Hasher.Value).ComputeHash(inputBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the SHA-512 hash of the given string using UTF8 byte encoding.
|
||||
/// </summary>
|
||||
/// <param name="value">The input string.</param>
|
||||
/// <param name="createHasher">if set to <c>true</c> [create hasher].</param>
|
||||
/// <returns>
|
||||
/// The computes a Hash-based Message Authentication Code (HMAC)
|
||||
/// using the SHA512 hash function.
|
||||
/// </returns>
|
||||
public static byte[] ComputeSha512(this string value, bool createHasher = false)
|
||||
{
|
||||
var inputBytes = Encoding.UTF8.GetBytes(value);
|
||||
return (createHasher ? SHA512.Create() : SHA512Hasher.Value).ComputeHash(inputBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the given item
|
||||
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
|
||||
/// overload exists.
|
||||
/// </summary>
|
||||
/// <param name="obj">The item.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string ToStringInvariant(this object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return string.Empty;
|
||||
|
||||
var itemType = obj.GetType();
|
||||
|
||||
if (itemType == typeof(string))
|
||||
return obj as string;
|
||||
|
||||
return Definitions.BasicTypesInfo.ContainsKey(itemType)
|
||||
? Definitions.BasicTypesInfo[itemType].ToStringInvariant(obj)
|
||||
: obj.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string that represents the given item
|
||||
/// It tries to use InvariantCulture if the ToString(IFormatProvider)
|
||||
/// overload exists.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to get the string.</typeparam>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string ToStringInvariant<T>(this T item)
|
||||
{
|
||||
if (typeof(string) == typeof(T))
|
||||
return Equals(item, default(T)) ? string.Empty : item as string;
|
||||
|
||||
return ToStringInvariant(item as object);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the control characters from a string except for those specified.
|
||||
/// </summary>
|
||||
/// <param name="value">The input.</param>
|
||||
/// <param name="excludeChars">When specified, these characters will not be removed.</param>
|
||||
/// <returns>
|
||||
/// A string that represents the current object.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">input.</exception>
|
||||
public static string RemoveControlCharsExcept(this string value, params char[] excludeChars)
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
|
||||
if (excludeChars == null)
|
||||
excludeChars = new char[] { };
|
||||
|
||||
return new string(value
|
||||
.Where(c => char.IsControl(c) == false || excludeChars.Contains(c))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all control characters from a string, including new line sequences.
|
||||
/// </summary>
|
||||
/// <param name="value">The input.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
/// <exception cref="ArgumentNullException">input.</exception>
|
||||
public static string RemoveControlChars(this string value) => value.RemoveControlCharsExcept(null);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs JSON string representing this object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="format">if set to <c>true</c> format the output.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string ToJson(this object obj, bool format = true) =>
|
||||
obj == null ? string.Empty : Json.Serialize(obj, format);
|
||||
|
||||
/// <summary>
|
||||
/// Returns text representing the properties of the specified object in a human-readable format.
|
||||
/// While this method is fairly expensive computationally speaking, it provides an easy way to
|
||||
/// examine objects.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string Stringify(this object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
return "(null)";
|
||||
|
||||
try
|
||||
{
|
||||
var jsonText = Json.Serialize(obj, false, "$type");
|
||||
var jsonData = Json.Deserialize(jsonText);
|
||||
|
||||
return new HumanizeJson(jsonData, 0).GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return obj.ToStringInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a section of the string, inclusive of both, the start and end indexes.
|
||||
/// This behavior is unlike JavaScript's Slice behavior where the end index is non-inclusive
|
||||
/// If the string is null it returns an empty string.
|
||||
/// </summary>
|
||||
/// <param name="value">The string.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="endIndex">The end index.</param>
|
||||
/// <returns>Retrieves a substring from this instance.</returns>
|
||||
public static string Slice(this string value, int startIndex, int endIndex)
|
||||
{
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
|
||||
var end = endIndex.Clamp(startIndex, value.Length - 1);
|
||||
|
||||
return startIndex >= end ? string.Empty : value.Substring(startIndex, (end - startIndex) + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a part of the string clamping the length and startIndex parameters to safe values.
|
||||
/// If the string is null it returns an empty string. This is basically just a safe version
|
||||
/// of string.Substring.
|
||||
/// </summary>
|
||||
/// <param name="str">The string.</param>
|
||||
/// <param name="startIndex">The start index.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <returns>Retrieves a substring from this instance.</returns>
|
||||
public static string SliceLength(this string str, int startIndex, int length)
|
||||
{
|
||||
if (str == null)
|
||||
return string.Empty;
|
||||
|
||||
var start = startIndex.Clamp(0, str.Length - 1);
|
||||
var len = length.Clamp(0, str.Length - start);
|
||||
|
||||
return len == 0 ? string.Empty : str.Substring(start, len);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the specified text into r, n or rn separated lines.
|
||||
/// </summary>
|
||||
/// <param name="value">The text.</param>
|
||||
/// <returns>
|
||||
/// An array whose elements contain the substrings from this instance
|
||||
/// that are delimited by one or more characters in separator.
|
||||
/// </returns>
|
||||
public static string[] ToLines(this string value) =>
|
||||
value == null ? new string[] { } : SplitLinesRegex.Value.Split(value);
|
||||
|
||||
/// <summary>
|
||||
/// Humanizes (make more human-readable) an identifier-style string
|
||||
/// in either camel case or snake case. For example, CamelCase will be converted to
|
||||
/// Camel Case and Snake_Case will be converted to Snake Case.
|
||||
/// </summary>
|
||||
/// <param name="value">The identifier-style string.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string Humanize(this string value)
|
||||
{
|
||||
if (value == null)
|
||||
return string.Empty;
|
||||
|
||||
var returnValue = UnderscoreRegex.Value.Replace(value, " ");
|
||||
returnValue = CamelCaseRegEx.Value.Replace(returnValue, SplitCamelCaseString.Value);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indents the specified multi-line text with the given amount of leading spaces
|
||||
/// per line.
|
||||
/// </summary>
|
||||
/// <param name="value">The text.</param>
|
||||
/// <param name="spaces">The spaces.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents the current object.</returns>
|
||||
public static string Indent(this string value, int spaces = 4)
|
||||
{
|
||||
if (value == null) value = string.Empty;
|
||||
if (spaces <= 0) return value;
|
||||
|
||||
var lines = value.ToLines();
|
||||
var builder = new StringBuilder();
|
||||
var indentStr = new string(' ', spaces);
|
||||
|
||||
foreach (var line in lines)
|
||||
{
|
||||
builder.AppendLine($"{indentStr}{line}");
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the line and column number (i.e. not index) of the
|
||||
/// specified character index. Useful to locate text in a multi-line
|
||||
/// string the same way a text editor does.
|
||||
/// Please not that the tuple contains first the line number and then the
|
||||
/// column number.
|
||||
/// </summary>
|
||||
/// <param name="value">The string.</param>
|
||||
/// <param name="charIndex">Index of the character.</param>
|
||||
/// <returns>A 2-tuple whose value is (item1, item2).</returns>
|
||||
public static Tuple<int, int> TextPositionAt(this string value, int charIndex)
|
||||
{
|
||||
if (value == null)
|
||||
return Tuple.Create(0, 0);
|
||||
|
||||
var index = charIndex.Clamp(0, value.Length - 1);
|
||||
|
||||
var lineIndex = 0;
|
||||
var colNumber = 0;
|
||||
|
||||
for (var i = 0; i <= index; i++)
|
||||
{
|
||||
if (value[i] == '\n')
|
||||
{
|
||||
lineIndex++;
|
||||
colNumber = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value[i] != '\r')
|
||||
colNumber++;
|
||||
}
|
||||
|
||||
return Tuple.Create(lineIndex + 1, colNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the file name system safe.
|
||||
/// </summary>
|
||||
/// <param name="value">The s.</param>
|
||||
/// <returns>
|
||||
/// A string with a safe file name.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">s.</exception>
|
||||
public static string ToSafeFilename(this string value)
|
||||
{
|
||||
return value == null
|
||||
? throw new ArgumentNullException(nameof(value))
|
||||
: InvalidFilenameChars.Value
|
||||
.Aggregate(value, (current, c) => current.Replace(c, string.Empty))
|
||||
.Slice(0, 220);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats a long into the closest bytes string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes length.</param>
|
||||
/// <returns>
|
||||
/// The string representation of the current Byte object, formatted as specified by the format parameter.
|
||||
/// </returns>
|
||||
public static string FormatBytes(this long bytes) => ((ulong) bytes).FormatBytes();
|
||||
|
||||
/// <summary>
|
||||
/// Formats a long into the closest bytes string.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes length.</param>
|
||||
/// <returns>
|
||||
/// A copy of format in which the format items have been replaced by the string
|
||||
/// representations of the corresponding arguments.
|
||||
/// </returns>
|
||||
public static string FormatBytes(this ulong bytes)
|
||||
{
|
||||
int i;
|
||||
double dblSByte = bytes;
|
||||
|
||||
for (i = 0; i < ByteSuffixes.Length && bytes >= 1024; i++, bytes /= 1024)
|
||||
{
|
||||
dblSByte = bytes / 1024.0;
|
||||
}
|
||||
|
||||
return $"{dblSByte:0.##} {ByteSuffixes[i]}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates the specified value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="maximumLength">The maximum length.</param>
|
||||
/// <returns>
|
||||
/// Retrieves a substring from this instance.
|
||||
/// The substring starts at a specified character position and has a specified length.
|
||||
/// </returns>
|
||||
public static string Truncate(this string value, int maximumLength) =>
|
||||
Truncate(value, maximumLength, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Truncates the specified value and append the omission last.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="maximumLength">The maximum length.</param>
|
||||
/// <param name="omission">The omission.</param>
|
||||
/// <returns>
|
||||
/// Retrieves a substring from this instance.
|
||||
/// The substring starts at a specified character position and has a specified length.
|
||||
/// </returns>
|
||||
public static string Truncate(this string value, int maximumLength, string omission)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return value.Length > maximumLength
|
||||
? value.Substring(0, maximumLength) + (omission ?? string.Empty)
|
||||
: value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> contains any of characters in
|
||||
/// the specified array of <see cref="char"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
/// <param name="chars">
|
||||
/// An array of <see cref="char"/> that contains characters to find.
|
||||
/// </param>
|
||||
public static bool Contains(this string value, params char[] chars) =>
|
||||
chars?.Length == 0 || (!string.IsNullOrEmpty(value) && value.IndexOfAny(chars) > -1);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces all chars in a string.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="replaceValue">The replace value.</param>
|
||||
/// <param name="chars">The chars.</param>
|
||||
/// <returns>The string with the characters replaced.</returns>
|
||||
public static string ReplaceAll(this string value, string replaceValue, params char[] chars) =>
|
||||
chars.Aggregate(value, (current, c) => current.Replace(new string(new[] {c}), replaceValue));
|
||||
|
||||
/// <summary>
|
||||
/// Convert hex character to an integer. Return -1 if char is something
|
||||
/// other than a hex char.
|
||||
/// </summary>
|
||||
/// <param name="value">The c.</param>
|
||||
/// <returns>Converted integer.</returns>
|
||||
public static int Hex2Int(this char value)
|
||||
{
|
||||
return value >= '0' && value <= '9'
|
||||
? value - '0'
|
||||
: value >= 'A' && value <= 'F'
|
||||
? value - 'A' + 10
|
||||
: value >= 'a' && value <= 'f'
|
||||
? value - 'a' + 10
|
||||
: -1;
|
||||
}
|
||||
}
|
||||
}
|
168
Unosquare.Swan.Lite/Extensions.ValueTypes.cs
Normal file
168
Unosquare.Swan.Lite/Extensions.ValueTypes.cs
Normal file
@ -0,0 +1,168 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Provides various extension methods for value types and structs.
|
||||
/// </summary>
|
||||
public static class ValueTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Clamps the specified value between the minimum and the maximum.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to clamp.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="min">The minimum.</param>
|
||||
/// <param name="max">The maximum.</param>
|
||||
/// <returns>A value that indicates the relative order of the objects being compared.</returns>
|
||||
public static T Clamp<T>(this T value, T min, T max)
|
||||
where T : struct, IComparable
|
||||
{
|
||||
if (value.CompareTo(min) < 0) return min;
|
||||
|
||||
return value.CompareTo(max) > 0 ? max : value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the specified value between the minimum and the maximum.
|
||||
/// </summary>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="min">The minimum.</param>
|
||||
/// <param name="max">The maximum.</param>
|
||||
/// <returns>A value that indicates the relative order of the objects being compared.</returns>
|
||||
public static int Clamp(this int value, int min, int max)
|
||||
=> value < min ? min : (value > max ? max : value);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified value is between a minimum and a maximum value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of value to check.</typeparam>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <param name="min">The minimum.</param>
|
||||
/// <param name="max">The maximum.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified minimum is between; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsBetween<T>(this T value, T min, T max)
|
||||
where T : struct, IComparable
|
||||
{
|
||||
return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes into the given struct type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structure to convert.</typeparam>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <returns>a struct type derived from convert an array of bytes ref=ToStruct".</returns>
|
||||
public static T ToStruct<T>(this byte[] data)
|
||||
where T : struct
|
||||
{
|
||||
return ToStruct<T>(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an array of bytes into the given struct type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structure to convert.</typeparam>
|
||||
/// <param name="data">The data.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="length">The length.</param>
|
||||
/// <returns>
|
||||
/// A managed object containing the data pointed to by the ptr parameter.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">data.</exception>
|
||||
public static T ToStruct<T>(this byte[] data, int offset, int length)
|
||||
where T : struct
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
|
||||
var buffer = new byte[length];
|
||||
Array.Copy(data, offset, buffer, 0, buffer.Length);
|
||||
var handle = GCHandle.Alloc(GetStructBytes<T>(buffer), GCHandleType.Pinned);
|
||||
|
||||
try
|
||||
{
|
||||
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a struct to an array of bytes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of structure to convert.</typeparam>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>A byte array containing the results of encoding the specified set of characters.</returns>
|
||||
public static byte[] ToBytes<T>(this T obj)
|
||||
where T : struct
|
||||
{
|
||||
var data = new byte[Marshal.SizeOf(obj)];
|
||||
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
|
||||
try
|
||||
{
|
||||
Marshal.StructureToPtr(obj, handle.AddrOfPinnedObject(), false);
|
||||
return GetStructBytes<T>(data);
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the endianness of an unsigned long to an unsigned integer.
|
||||
/// </summary>
|
||||
/// <param name="longBytes">The bytes contained in a long.</param>
|
||||
/// <returns>
|
||||
/// A 32-bit unsigned integer equivalent to the ulong
|
||||
/// contained in longBytes.
|
||||
/// </returns>
|
||||
public static uint SwapEndianness(this ulong longBytes)
|
||||
=> (uint) (((longBytes & 0x000000ff) << 24) +
|
||||
((longBytes & 0x0000ff00) << 8) +
|
||||
((longBytes & 0x00ff0000) >> 8) +
|
||||
((longBytes & 0xff000000) >> 24));
|
||||
|
||||
private static byte[] GetStructBytes<T>(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
|
||||
#if !NETSTANDARD1_3
|
||||
var fields = typeof(T).GetTypeInfo()
|
||||
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
#else
|
||||
var fields = typeof(T).GetTypeInfo().DeclaredFields;
|
||||
#endif
|
||||
var endian = Runtime.AttributeCache.RetrieveOne<StructEndiannessAttribute, T>();
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
if (endian == null && !field.IsDefined(typeof(StructEndiannessAttribute), false))
|
||||
continue;
|
||||
|
||||
var offset = Marshal.OffsetOf<T>(field.Name).ToInt32();
|
||||
var length = Marshal.SizeOf(field.FieldType);
|
||||
|
||||
endian = endian ?? Runtime.AttributeCache.RetrieveOne<StructEndiannessAttribute>(field);
|
||||
|
||||
if (endian != null && (endian.Endianness == Endianness.Big && BitConverter.IsLittleEndian ||
|
||||
endian.Endianness == Endianness.Little && !BitConverter.IsLittleEndian))
|
||||
{
|
||||
Array.Reverse(data, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
331
Unosquare.Swan.Lite/Extensions.cs
Normal file
331
Unosquare.Swan.Lite/Extensions.cs
Normal file
@ -0,0 +1,331 @@
|
||||
namespace Unosquare.Swan
|
||||
{
|
||||
using Attributes;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// </summary>
|
||||
public static partial class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Iterates over the public, instance, readable properties of the source and
|
||||
/// tries to write a compatible value to a public, instance, writable property in the destination.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the source.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>Number of properties that was copied successful.</returns>
|
||||
public static int CopyPropertiesTo<T>(this T source, object target)
|
||||
where T : class
|
||||
{
|
||||
var copyable = GetCopyableProperties(target);
|
||||
return copyable.Any()
|
||||
? CopyOnlyPropertiesTo(source, target, copyable.ToArray())
|
||||
: CopyPropertiesTo(source, target, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the public, instance, readable properties of the source and
|
||||
/// tries to write a compatible value to a public, instance, writable property in the destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The destination.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// Number of properties that were successfully copied.
|
||||
/// </returns>
|
||||
public static int CopyPropertiesTo(this object source, object target, string[] ignoreProperties = null)
|
||||
=> Components.ObjectMapper.Copy(source, target, null, ignoreProperties);
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the public, instance, readable properties of the source and
|
||||
/// tries to write a compatible value to a public, instance, writable property in the destination.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the source.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>Number of properties that was copied successful.</returns>
|
||||
public static int CopyOnlyPropertiesTo<T>(this T source, object target)
|
||||
where T : class
|
||||
{
|
||||
return CopyOnlyPropertiesTo(source, target, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the public, instance, readable properties of the source and
|
||||
/// tries to write a compatible value to a public, instance, writable property in the destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The destination.</param>
|
||||
/// <param name="propertiesToCopy">Properties to copy.</param>
|
||||
/// <returns>
|
||||
/// Number of properties that were successfully copied.
|
||||
/// </returns>
|
||||
public static int CopyOnlyPropertiesTo(this object source, object target, string[] propertiesToCopy)
|
||||
{
|
||||
return Components.ObjectMapper.Copy(source, target, propertiesToCopy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the properties to new instance of T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The new object type.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// The specified type with properties copied.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
public static T DeepClone<T>(this T source, string[] ignoreProperties = null)
|
||||
where T : class
|
||||
{
|
||||
return source.CopyPropertiesToNew<T>(ignoreProperties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the properties to new instance of T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The new object type.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="ignoreProperties">The ignore properties.</param>
|
||||
/// <returns>
|
||||
/// The specified type with properties copied.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
public static T CopyPropertiesToNew<T>(this object source, string[] ignoreProperties = null)
|
||||
where T : class
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
var target = Activator.CreateInstance<T>();
|
||||
var copyable = target.GetCopyableProperties();
|
||||
|
||||
if (copyable.Any())
|
||||
source.CopyOnlyPropertiesTo(target, copyable.ToArray());
|
||||
else
|
||||
source.CopyPropertiesTo(target, ignoreProperties);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the only properties to new instance of T.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Object Type.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="propertiesToCopy">The properties to copy.</param>
|
||||
/// <returns>
|
||||
/// The specified type with properties copied.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
public static T CopyOnlyPropertiesToNew<T>(this object source, string[] propertiesToCopy)
|
||||
where T : class
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
var target = Activator.CreateInstance<T>();
|
||||
source.CopyOnlyPropertiesTo(target, propertiesToCopy);
|
||||
return target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates over the keys of the source and tries to write a compatible value to a public,
|
||||
/// instance, writable property in the destination.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="ignoreKeys">The ignore keys.</param>
|
||||
/// <returns>Number of properties that was copied successful.</returns>
|
||||
public static int CopyKeyValuePairTo(
|
||||
this IDictionary<string, object> source,
|
||||
object target,
|
||||
string[] ignoreKeys = null)
|
||||
{
|
||||
return Components.ObjectMapper.Copy(source, target, null, ignoreKeys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Measures the elapsed time of the given action as a TimeSpan
|
||||
/// This method uses a high precision Stopwatch.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>
|
||||
/// A time interval that represents a specified time, where the specification is in units of ticks.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">target.</exception>
|
||||
public static TimeSpan Benchmark(this Action target)
|
||||
{
|
||||
if (target == null)
|
||||
throw new ArgumentNullException(nameof(target));
|
||||
|
||||
var sw = new Stopwatch();
|
||||
|
||||
try
|
||||
{
|
||||
sw.Start();
|
||||
target.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// swallow
|
||||
}
|
||||
finally
|
||||
{
|
||||
sw.Stop();
|
||||
}
|
||||
|
||||
return TimeSpan.FromTicks(sw.ElapsedTicks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the specified action.
|
||||
/// </summary>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <param name="retryInterval">The retry interval.</param>
|
||||
/// <param name="retryCount">The retry count.</param>
|
||||
public static void Retry(
|
||||
this Action action,
|
||||
TimeSpan retryInterval = default,
|
||||
int retryCount = 3)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
Retry<object>(() =>
|
||||
{
|
||||
action();
|
||||
return null;
|
||||
},
|
||||
retryInterval,
|
||||
retryCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the specified action.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the source.</typeparam>
|
||||
/// <param name="action">The action.</param>
|
||||
/// <param name="retryInterval">The retry interval.</param>
|
||||
/// <param name="retryCount">The retry count.</param>
|
||||
/// <returns>
|
||||
/// The return value of the method that this delegate encapsulates.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">action.</exception>
|
||||
/// <exception cref="AggregateException">Represents one or many errors that occur during application execution.</exception>
|
||||
public static T Retry<T>(
|
||||
this Func<T> action,
|
||||
TimeSpan retryInterval = default,
|
||||
int retryCount = 3)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
if (retryInterval == default)
|
||||
retryInterval = TimeSpan.FromSeconds(1);
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
for (var retry = 0; retry < retryCount; retry++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (retry > 0)
|
||||
Task.Delay(retryInterval).Wait();
|
||||
|
||||
return action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the exception message, plus all the inner exception messages separated by new lines.
|
||||
/// </summary>
|
||||
/// <param name="ex">The ex.</param>
|
||||
/// <param name="priorMessage">The prior message.</param>
|
||||
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
|
||||
public static string ExceptionMessage(this Exception ex, string priorMessage = "")
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (ex == null)
|
||||
throw new ArgumentNullException(nameof(ex));
|
||||
|
||||
var fullMessage = string.IsNullOrWhiteSpace(priorMessage)
|
||||
? ex.Message
|
||||
: priorMessage + "\r\n" + ex.Message;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(ex.InnerException?.Message))
|
||||
return fullMessage;
|
||||
|
||||
ex = ex.InnerException;
|
||||
priorMessage = fullMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the copyable properties.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// Array of properties.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">model.</exception>
|
||||
public static IEnumerable<string> GetCopyableProperties(this object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
return Runtime.PropertyTypeCache
|
||||
.RetrieveAllProperties(obj.GetType(), true)
|
||||
.Select(x => new { x.Name, HasAttribute = Runtime.AttributeCache.RetrieveOne<CopyableAttribute>(x) != null})
|
||||
.Where(x => x.HasAttribute)
|
||||
.Select(x => x.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the object is valid.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the specified model is valid; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool IsValid(this object obj) => Runtime.ObjectValidator.IsValid(obj);
|
||||
|
||||
internal static void CreateTarget(
|
||||
this object source,
|
||||
Type targetType,
|
||||
bool includeNonPublic,
|
||||
ref object target)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
case string _:
|
||||
break; // do nothing. Simply skip creation
|
||||
case IList sourceObjectList when targetType.IsArray: // When using arrays, there is no default constructor, attempt to build a compatible array
|
||||
var elementType = targetType.GetElementType();
|
||||
|
||||
if (elementType != null)
|
||||
target = Array.CreateInstance(elementType, sourceObjectList.Count);
|
||||
break;
|
||||
default:
|
||||
target = Activator.CreateInstance(targetType, includeNonPublic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
650
Unosquare.Swan.Lite/Formatters/CsvReader.cs
Normal file
650
Unosquare.Swan.Lite/Formatters/CsvReader.cs
Normal file
@ -0,0 +1,650 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a reader designed for CSV text.
|
||||
/// It is capable of deserializing objects from individual lines of CSV text,
|
||||
/// transforming CSV lines of text into objects,
|
||||
/// or simply reading the lines of CSV as an array of strings.
|
||||
/// </summary>
|
||||
/// <seealso cref="System.IDisposable" />
|
||||
/// <example>
|
||||
/// The following example describes how to load a list of objects from a CSV file.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // load records from a CSV file
|
||||
/// var loadedRecords =
|
||||
/// CsvReader.LoadRecords<Person>("C:\\Users\\user\\Documents\\file.csv");
|
||||
///
|
||||
/// // loadedRecords =
|
||||
/// // [
|
||||
/// // { Age = 20, Name = "George" }
|
||||
/// // { Age = 18, Name = "Juan" }
|
||||
/// // ]
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// The following code explains how to read a CSV formatted string.
|
||||
/// <code>
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
/// using System.Text;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // data to be read
|
||||
/// var data = @"Company,OpenPositions,MainTechnology,Revenue
|
||||
/// Co,2,""C#, MySQL, JavaScript, HTML5 and CSS3"",500
|
||||
/// Ca,2,""C#, MySQL, JavaScript, HTML5 and CSS3"",600";
|
||||
///
|
||||
/// using(var stream = new MemoryStream(Encoding.UTF8.GetBytes(data)))
|
||||
/// {
|
||||
/// // create a CSV reader
|
||||
/// var reader = new CsvReader(stream, false, Encoding.UTF8);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class CsvReader : IDisposable
|
||||
{
|
||||
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
private ulong _count;
|
||||
private char _escapeCharacter = '"';
|
||||
private char _separatorCharacter = ',';
|
||||
|
||||
private bool _hasDisposed; // To detect redundant calls
|
||||
private string[] _headings;
|
||||
private Dictionary<string, string> _defaultMap;
|
||||
private StreamReader _reader;
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvReader" /> class.
|
||||
/// </summary>
|
||||
/// <param name="inputStream">The stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> leaves the input stream open.</param>
|
||||
/// <param name="textEncoding">The text encoding.</param>
|
||||
public CsvReader(Stream inputStream, bool leaveOpen, Encoding textEncoding)
|
||||
{
|
||||
if (inputStream == null)
|
||||
throw new ArgumentNullException(nameof(inputStream));
|
||||
|
||||
if (textEncoding == null)
|
||||
throw new ArgumentNullException(nameof(textEncoding));
|
||||
|
||||
_reader = new StreamReader(inputStream, textEncoding, true, 2048, leaveOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvReader"/> class.
|
||||
/// It will automatically close the stream upon disposing.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="textEncoding">The text encoding.</param>
|
||||
public CsvReader(Stream stream, Encoding textEncoding)
|
||||
: this(stream, false, textEncoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvReader"/> class.
|
||||
/// It automatically closes the stream when disposing this reader
|
||||
/// and uses the Windows 1253 encoding.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream.</param>
|
||||
public CsvReader(Stream stream)
|
||||
: this(stream, false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvReader"/> class.
|
||||
/// It uses the Windows 1252 Encoding by default and it automatically closes the file
|
||||
/// when this reader is disposed of.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsvReader(string filename)
|
||||
: this(File.OpenRead(filename), false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvReader"/> class.
|
||||
/// It automatically closes the file when disposing this reader.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvReader(string filename, Encoding encoding)
|
||||
: this(File.OpenRead(filename), false, encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of lines that have been read, including the headings.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public ulong Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
return _count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the escape character.
|
||||
/// By default it is the double quote '"'.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The escape character.
|
||||
/// </value>
|
||||
public char EscapeCharacter
|
||||
{
|
||||
get => _escapeCharacter;
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
_escapeCharacter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the separator character.
|
||||
/// By default it is the comma character ','.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The separator character.
|
||||
/// </value>
|
||||
public char SeparatorCharacter
|
||||
{
|
||||
get => _separatorCharacter;
|
||||
set
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
_separatorCharacter = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the stream reader is at the end of the stream
|
||||
/// In other words, if no more data can be read, this will be set to true.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if [end of stream]; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool EndOfStream
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
return _reader.EndOfStream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generic, Main ReadLine method
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text into an array of strings.
|
||||
/// </summary>
|
||||
/// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
public string[] ReadLine()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_reader.EndOfStream)
|
||||
throw new EndOfStreamException("Cannot read past the end of the stream");
|
||||
|
||||
var values = ParseRecord(_reader, _escapeCharacter, _separatorCharacter);
|
||||
_count++;
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read Methods
|
||||
|
||||
/// <summary>
|
||||
/// Skips a line of CSV text.
|
||||
/// This operation does not increment the Count property and it is useful when you need to read the headings
|
||||
/// skipping over a few lines as Reading headings is only supported
|
||||
/// as the first read operation (i.e. while count is still 0).
|
||||
/// </summary>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
public void SkipRecord()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_reader.EndOfStream)
|
||||
throw new EndOfStreamException("Cannot read past the end of the stream");
|
||||
|
||||
ParseRecord(_reader, _escapeCharacter, _separatorCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text and stores the values read as a representation of the column names
|
||||
/// to be used for parsing objects. You have to call this method before calling ReadObject methods.
|
||||
/// </summary>
|
||||
/// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">
|
||||
/// Reading headings is only supported as the first read operation.
|
||||
/// or
|
||||
/// ReadHeadings.
|
||||
/// </exception>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
public string[] ReadHeadings()
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_headings != null)
|
||||
throw new InvalidOperationException($"The {nameof(ReadHeadings)} method had already been called.");
|
||||
|
||||
if (_count != 0)
|
||||
throw new InvalidOperationException("Reading headings is only supported as the first read operation.");
|
||||
|
||||
_headings = ReadLine();
|
||||
_defaultMap = _headings.ToDictionary(x => x, x => x);
|
||||
|
||||
return _headings.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text, converting it into a dynamic object in which properties correspond to the names of the headings.
|
||||
/// </summary>
|
||||
/// <param name="map">The mappings between CSV headings (keys) and object properties (values).</param>
|
||||
/// <returns>Object of the type of the elements in the collection of key/value pairs.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
/// <exception cref="System.ArgumentNullException">map.</exception>
|
||||
public IDictionary<string, object> ReadObject(IDictionary<string, string> map)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
if (_headings == null)
|
||||
throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object.");
|
||||
|
||||
if (map == null)
|
||||
throw new ArgumentNullException(nameof(map));
|
||||
|
||||
var result = new Dictionary<string, object>();
|
||||
var values = ReadLine();
|
||||
|
||||
for (var i = 0; i < _headings.Length; i++)
|
||||
{
|
||||
if (i > values.Length - 1)
|
||||
break;
|
||||
|
||||
result[_headings[i]] = values[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text, converting it into a dynamic object
|
||||
/// The property names correspond to the names of the CSV headings.
|
||||
/// </summary>
|
||||
/// <returns>Object of the type of the elements in the collection of key/value pairs.</returns>
|
||||
public IDictionary<string, object> ReadObject() => ReadObject(_defaultMap);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text converting it into an object of the given type, using a map (or Dictionary)
|
||||
/// where the keys are the names of the headings and the values are the names of the instance properties
|
||||
/// in the given Type. The result object must be already instantiated.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to map.</typeparam>
|
||||
/// <param name="map">The map.</param>
|
||||
/// <param name="result">The result.</param>
|
||||
/// <exception cref="System.ArgumentNullException">map
|
||||
/// or
|
||||
/// result.</exception>
|
||||
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
public void ReadObject<T>(IDictionary<string, string> map, ref T result)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
// Check arguments
|
||||
{
|
||||
if (map == null)
|
||||
throw new ArgumentNullException(nameof(map));
|
||||
|
||||
if (_reader.EndOfStream)
|
||||
throw new EndOfStreamException("Cannot read past the end of the stream");
|
||||
|
||||
if (_headings == null)
|
||||
throw new InvalidOperationException($"Call the {nameof(ReadHeadings)} method before reading as an object.");
|
||||
|
||||
if (Equals(result, default(T)))
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
// Read line and extract values
|
||||
var values = ReadLine();
|
||||
|
||||
// Extract properties from cache
|
||||
var properties = TypeCache
|
||||
.RetrieveFilteredProperties(typeof(T), true, x => x.CanWrite && Definitions.BasicTypesInfo.ContainsKey(x.PropertyType));
|
||||
|
||||
// Assign property values for each heading
|
||||
for (var i = 0; i < _headings.Length; i++)
|
||||
{
|
||||
// break if no more headings are matched
|
||||
if (i > values.Length - 1)
|
||||
break;
|
||||
|
||||
// skip if no heading is available or the heading is empty
|
||||
if (map.ContainsKey(_headings[i]) == false &&
|
||||
string.IsNullOrWhiteSpace(map[_headings[i]]) == false)
|
||||
continue;
|
||||
|
||||
// Prepare the target property
|
||||
var propertyName = map[_headings[i]];
|
||||
|
||||
// Parse and assign the basic type value to the property if exists
|
||||
properties
|
||||
.FirstOrDefault(p => p.Name.Equals(propertyName))?
|
||||
.TrySetBasicType(values[i], result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text converting it into an object of the given type, using a map (or Dictionary)
|
||||
/// where the keys are the names of the headings and the values are the names of the instance properties
|
||||
/// in the given Type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to map.</typeparam>
|
||||
/// <param name="map">The map of CSV headings (keys) and Type property names (values).</param>
|
||||
/// <returns>The conversion of specific type of object.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">map.</exception>
|
||||
/// <exception cref="System.InvalidOperationException">ReadHeadings.</exception>
|
||||
/// <exception cref="System.IO.EndOfStreamException">Cannot read past the end of the stream.</exception>
|
||||
public T ReadObject<T>(IDictionary<string, string> map)
|
||||
where T : new()
|
||||
{
|
||||
var result = Activator.CreateInstance<T>();
|
||||
ReadObject(map, ref result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a line of CSV text converting it into an object of the given type, and assuming
|
||||
/// the property names of the target type match the heading names of the file.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object.</typeparam>
|
||||
/// <returns>The conversion of specific type of object.</returns>
|
||||
public T ReadObject<T>()
|
||||
where T : new()
|
||||
{
|
||||
return ReadObject<T>(_defaultMap);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Support Methods
|
||||
|
||||
/// <summary>
|
||||
/// Parses a line of standard CSV text into an array of strings.
|
||||
/// Note that quoted values might have new line sequences in them. Field values will contain such sequences.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader.</param>
|
||||
/// <param name="escapeCharacter">The escape character.</param>
|
||||
/// <param name="separatorCharacter">The separator character.</param>
|
||||
/// <returns>An array of the specified element type containing copies of the elements of the ArrayList.</returns>
|
||||
private static string[] ParseRecord(StreamReader reader, char escapeCharacter = '"', char separatorCharacter = ',')
|
||||
{
|
||||
var values = new List<string>();
|
||||
var currentValue = new StringBuilder(1024);
|
||||
var currentState = ReadState.WaitingForNewField;
|
||||
string line;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
for (var charIndex = 0; charIndex < line.Length; charIndex++)
|
||||
{
|
||||
// Get the current and next character
|
||||
var currentChar = line[charIndex];
|
||||
var nextChar = charIndex < line.Length - 1 ? line[charIndex + 1] : new char?();
|
||||
|
||||
// Perform logic based on state and decide on next state
|
||||
switch (currentState)
|
||||
{
|
||||
case ReadState.WaitingForNewField:
|
||||
{
|
||||
currentValue.Clear();
|
||||
|
||||
if (currentChar == escapeCharacter)
|
||||
{
|
||||
currentState = ReadState.PushingQuoted;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentChar == separatorCharacter)
|
||||
{
|
||||
values.Add(currentValue.ToString());
|
||||
currentState = ReadState.WaitingForNewField;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentValue.Append(currentChar);
|
||||
currentState = ReadState.PushingNormal;
|
||||
continue;
|
||||
}
|
||||
|
||||
case ReadState.PushingNormal:
|
||||
{
|
||||
// Handle field content delimiter by comma
|
||||
if (currentChar == separatorCharacter)
|
||||
{
|
||||
currentState = ReadState.WaitingForNewField;
|
||||
values.Add(currentValue.ToString());
|
||||
currentValue.Clear();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle double quote escaping
|
||||
if (currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter)
|
||||
{
|
||||
// advance 1 character now. The loop will advance one more.
|
||||
currentValue.Append(currentChar);
|
||||
charIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentValue.Append(currentChar);
|
||||
break;
|
||||
}
|
||||
|
||||
case ReadState.PushingQuoted:
|
||||
{
|
||||
// Handle field content delimiter by ending double quotes
|
||||
if (currentChar == escapeCharacter && (nextChar.HasValue == false || nextChar != escapeCharacter))
|
||||
{
|
||||
currentState = ReadState.PushingNormal;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle double quote escaping
|
||||
if (currentChar == escapeCharacter && nextChar.HasValue && nextChar == escapeCharacter)
|
||||
{
|
||||
// advance 1 character now. The loop will advance one more.
|
||||
currentValue.Append(currentChar);
|
||||
charIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentValue.Append(currentChar);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine if we need to continue reading a new line if it is part of the quoted
|
||||
// field value
|
||||
if (currentState == ReadState.PushingQuoted)
|
||||
{
|
||||
// we need to add the new line sequence to the output of the field
|
||||
// because we were pushing a quoted value
|
||||
currentValue.Append(Environment.NewLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
// push anything that has not been pushed (flush) into a last value
|
||||
values.Add(currentValue.ToString());
|
||||
currentValue.Clear();
|
||||
|
||||
// stop reading more lines we have reached the end of the CSV record
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we ended up pushing quoted and no closing quotes we might
|
||||
// have additional text in yt
|
||||
if (currentValue.Length > 0)
|
||||
{
|
||||
values.Add(currentValue.ToString());
|
||||
}
|
||||
|
||||
return values.ToArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Loads the records from the stream
|
||||
/// This method uses Windows 1252 encoding.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IList items to load.</typeparam>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <returns>A generic collection of objects that can be individually accessed by index.</returns>
|
||||
public static IList<T> LoadRecords<T>(Stream stream)
|
||||
where T : new()
|
||||
{
|
||||
var result = new List<T>();
|
||||
|
||||
using (var reader = new CsvReader(stream))
|
||||
{
|
||||
reader.ReadHeadings();
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
result.Add(reader.ReadObject<T>());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the records from the give file path.
|
||||
/// This method uses Windows 1252 encoding.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of IList items to load.</typeparam>
|
||||
/// <param name="filePath">The file path.</param>
|
||||
/// <returns>A generic collection of objects that can be individually accessed by index.</returns>
|
||||
public static IList<T> LoadRecords<T>(string filePath)
|
||||
where T : new()
|
||||
{
|
||||
return LoadRecords<T>(File.OpenRead(filePath));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_hasDisposed) return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
_reader.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_reader = null;
|
||||
}
|
||||
}
|
||||
|
||||
_hasDisposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Defines the 3 different read states
|
||||
/// for the parsing state machine.
|
||||
/// </summary>
|
||||
private enum ReadState
|
||||
{
|
||||
WaitingForNewField,
|
||||
PushingNormal,
|
||||
PushingQuoted,
|
||||
}
|
||||
}
|
||||
}
|
478
Unosquare.Swan.Lite/Formatters/CsvWriter.cs
Normal file
478
Unosquare.Swan.Lite/Formatters/CsvWriter.cs
Normal file
@ -0,0 +1,478 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using Reflection;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// A CSV writer useful for exporting a set of objects.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// The following code describes how to save a list of objects into a CSV file.
|
||||
/// <code>
|
||||
/// using System.Collections.Generic;
|
||||
/// using Unosquare.Swan.Formatters;
|
||||
///
|
||||
/// class Example
|
||||
/// {
|
||||
/// class Person
|
||||
/// {
|
||||
/// public string Name { get; set; }
|
||||
/// public int Age { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// static void Main()
|
||||
/// {
|
||||
/// // create a list of people
|
||||
/// var people = new List<Person>
|
||||
/// {
|
||||
/// new Person { Name = "Artyom", Age = 20 },
|
||||
/// new Person { Name = "Aloy", Age = 18 }
|
||||
/// }
|
||||
///
|
||||
/// // write items inside file.csv
|
||||
/// CsvWriter.SaveRecords(people, "C:\\Users\\user\\Documents\\file.csv");
|
||||
///
|
||||
/// // output
|
||||
/// // | Name | Age |
|
||||
/// // | Artyom | 20 |
|
||||
/// // | Aloy | 18 |
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class CsvWriter : IDisposable
|
||||
{
|
||||
private static readonly PropertyTypeCache TypeCache = new PropertyTypeCache();
|
||||
|
||||
private readonly object _syncLock = new object();
|
||||
private readonly Stream _outputStream;
|
||||
private readonly Encoding _encoding;
|
||||
private readonly bool _leaveStreamOpen;
|
||||
private bool _isDisposing;
|
||||
private ulong _mCount;
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter" /> class.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="leaveOpen">if set to <c>true</c> [leave open].</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, bool leaveOpen, Encoding encoding)
|
||||
{
|
||||
_outputStream = outputStream;
|
||||
_encoding = encoding;
|
||||
_leaveStreamOpen = leaveOpen;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It automatically closes the stream when disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(Stream outputStream, Encoding encoding)
|
||||
: this(outputStream, false, encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It uses the Windows 1252 encoding and automatically closes
|
||||
/// the stream upon disposing this writer.
|
||||
/// </summary>
|
||||
/// <param name="outputStream">The output stream.</param>
|
||||
public CsvWriter(Stream outputStream)
|
||||
: this(outputStream, false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the Windows 1252 encoding.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
public CsvWriter(string filename)
|
||||
: this(File.OpenWrite(filename), false, Definitions.Windows1252Encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CsvWriter"/> class.
|
||||
/// It opens the file given file, automatically closes the stream upon
|
||||
/// disposing of this writer, and uses the given text encoding for output.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename.</param>
|
||||
/// <param name="encoding">The encoding.</param>
|
||||
public CsvWriter(string filename, Encoding encoding)
|
||||
: this(File.OpenWrite(filename), false, encoding)
|
||||
{
|
||||
// placeholder
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the field separator character.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The separator character.
|
||||
/// </value>
|
||||
public char SeparatorCharacter { get; set; } = ',';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the escape character to use to escape field values.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The escape character.
|
||||
/// </value>
|
||||
public char EscapeCharacter { get; set; } = '"';
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the new line character sequence to use when writing a line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The new line sequence.
|
||||
/// </value>
|
||||
public string NewLineSequence { get; set; } = Environment.NewLine;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a list of properties to ignore when outputting CSV lines.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The ignore property names.
|
||||
/// </value>
|
||||
public List<string> IgnorePropertyNames { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of lines that have been written, including the headings line.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The count.
|
||||
/// </value>
|
||||
public ulong Count
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
return _mCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a stream.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
/// <param name="truncateData"><c>true</c> if stream is truncated, default <c>false</c>.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static int SaveRecords<T>(IEnumerable<T> items, Stream stream, bool truncateData = false)
|
||||
{
|
||||
// truncate the file if it had data
|
||||
if (truncateData && stream.Length > 0)
|
||||
stream.SetLength(0);
|
||||
|
||||
using (var writer = new CsvWriter(stream))
|
||||
{
|
||||
writer.WriteHeadings<T>();
|
||||
writer.WriteObjects(items);
|
||||
return (int)writer.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items to a CSV file.
|
||||
/// If the file exits, it overwrites it. If it does not, it creates it.
|
||||
/// It uses the Windows 1252 text encoding for output.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of enumeration.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <param name="filePath">The file path.</param>
|
||||
/// <returns>Number of item saved.</returns>
|
||||
public static int SaveRecords<T>(IEnumerable<T> items, string filePath) => SaveRecords(items, File.OpenWrite(filePath), true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Generic, main Write Line Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params object[] items)
|
||||
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text. Items are converted to strings.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// If items are not string, the ToStringInvariant() method is called on them.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<object> items)
|
||||
=> WriteLine(items.Select(x => x == null ? string.Empty : x.ToStringInvariant()));
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(params string[] items) => WriteLine((IEnumerable<string>) items);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a line of CSV text.
|
||||
/// If items are found to be null, empty strings are written out.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteLine(IEnumerable<string> items)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
var length = items.Count();
|
||||
var separatorBytes = _encoding.GetBytes(new[] { SeparatorCharacter });
|
||||
var endOfLineBytes = _encoding.GetBytes(NewLineSequence);
|
||||
|
||||
// Declare state variables here to avoid recreation, allocation and
|
||||
// reassignment in every loop
|
||||
bool needsEnclosing;
|
||||
string textValue;
|
||||
byte[] output;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
textValue = items.ElementAt(i);
|
||||
|
||||
// Determine if we need the string to be enclosed
|
||||
// (it either contains an escape, new line, or separator char)
|
||||
needsEnclosing = textValue.IndexOf(SeparatorCharacter) >= 0
|
||||
|| textValue.IndexOf(EscapeCharacter) >= 0
|
||||
|| textValue.IndexOf('\r') >= 0
|
||||
|| textValue.IndexOf('\n') >= 0;
|
||||
|
||||
// Escape the escape characters by repeating them twice for every instance
|
||||
textValue = textValue.Replace($"{EscapeCharacter}",
|
||||
$"{EscapeCharacter}{EscapeCharacter}");
|
||||
|
||||
// Enclose the text value if we need to
|
||||
if (needsEnclosing)
|
||||
textValue = string.Format($"{EscapeCharacter}{textValue}{EscapeCharacter}", textValue);
|
||||
|
||||
// Get the bytes to write to the stream and write them
|
||||
output = _encoding.GetBytes(textValue);
|
||||
_outputStream.Write(output, 0, output.Length);
|
||||
|
||||
// only write a separator if we are moving in between values.
|
||||
// the last value should not be written.
|
||||
if (i < length - 1)
|
||||
_outputStream.Write(separatorBytes, 0, separatorBytes.Length);
|
||||
}
|
||||
|
||||
// output the newline sequence
|
||||
_outputStream.Write(endOfLineBytes, 0, endOfLineBytes.Length);
|
||||
_mCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Object Method
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <exception cref="System.ArgumentNullException">item.</exception>
|
||||
public void WriteObject(object item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
lock (_syncLock)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case IDictionary typedItem:
|
||||
WriteLine(GetFilteredDictionary(typedItem));
|
||||
return;
|
||||
case ICollection typedItem:
|
||||
WriteLine(typedItem.Cast<object>());
|
||||
return;
|
||||
default:
|
||||
WriteLine(GetFilteredTypeProperties(item.GetType())
|
||||
.Select(x => x.ToFormattedString(item)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a row of CSV text. It handles the special cases where the object is
|
||||
/// a dynamic object or and array. It also handles non-collection objects fine.
|
||||
/// If you do not like the way the output is handled, you can simply write an extension
|
||||
/// method of this class and use the WriteLine method instead.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="item">The item.</param>
|
||||
public void WriteObject<T>(T item) => WriteObject(item as object);
|
||||
|
||||
/// <summary>
|
||||
/// Writes a set of items, one per line and atomically by repeatedly calling the
|
||||
/// WriteObject method. For more info check out the description of the WriteObject
|
||||
/// method.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to write.</typeparam>
|
||||
/// <param name="items">The items.</param>
|
||||
public void WriteObjects<T>(IEnumerable<T> items)
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
foreach (var item in items)
|
||||
WriteObject(item);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Write Headings Methods
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of object to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">type.</exception>
|
||||
public void WriteHeadings(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var properties = GetFilteredTypeProperties(type).Select(p => p.Name).Cast<object>();
|
||||
WriteLine(properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to extract headings.</typeparam>
|
||||
public void WriteHeadings<T>() => WriteHeadings(typeof(T));
|
||||
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="dictionary">The dictionary to extract headings.</param>
|
||||
/// <exception cref="System.ArgumentNullException">dictionary.</exception>
|
||||
public void WriteHeadings(IDictionary dictionary)
|
||||
{
|
||||
if (dictionary == null)
|
||||
throw new ArgumentNullException(nameof(dictionary));
|
||||
|
||||
WriteLine(GetFilteredDictionary(dictionary, true));
|
||||
}
|
||||
|
||||
#if NET452
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="item">The object to extract headings.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
/// <exception cref="ArgumentException">Unable to cast dynamic object to a suitable dictionary - item</exception>
|
||||
public void WriteHeadings(dynamic item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (!(item is IDictionary<string, object> dictionary))
|
||||
throw new ArgumentException("Unable to cast dynamic object to a suitable dictionary", nameof(item));
|
||||
|
||||
WriteHeadings(dictionary);
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Writes the headings.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to extract headings.</param>
|
||||
/// <exception cref="ArgumentNullException">obj.</exception>
|
||||
public void WriteHeadings(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
WriteHeadings(obj.GetType());
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => Dispose(true);
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposeAlsoManaged"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposeAlsoManaged)
|
||||
{
|
||||
if (_isDisposing) return;
|
||||
|
||||
if (disposeAlsoManaged)
|
||||
{
|
||||
if (_leaveStreamOpen == false)
|
||||
{
|
||||
_outputStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_isDisposing = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Support Methods
|
||||
|
||||
private IEnumerable<string> GetFilteredDictionary(IDictionary dictionary, bool filterKeys = false)
|
||||
=> dictionary
|
||||
.Keys
|
||||
.Cast<object>()
|
||||
.Select(key => key == null ? string.Empty : key.ToStringInvariant())
|
||||
.Where(stringKey => !IgnorePropertyNames.Contains(stringKey))
|
||||
.Select(stringKey =>
|
||||
filterKeys
|
||||
? stringKey
|
||||
: dictionary[stringKey] == null ? string.Empty : dictionary[stringKey].ToStringInvariant());
|
||||
|
||||
private IEnumerable<PropertyInfo> GetFilteredTypeProperties(Type type)
|
||||
=> TypeCache.Retrieve(type, t =>
|
||||
t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(p => p.CanRead))
|
||||
.Where(p => !IgnorePropertyNames.Contains(p.Name));
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
150
Unosquare.Swan.Lite/Formatters/HumanizeJson.cs
Normal file
150
Unosquare.Swan.Lite/Formatters/HumanizeJson.cs
Normal file
@ -0,0 +1,150 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
internal class HumanizeJson
|
||||
{
|
||||
private readonly StringBuilder _builder = new StringBuilder();
|
||||
private readonly int _indent;
|
||||
private readonly string _indentStr;
|
||||
private readonly object _obj;
|
||||
|
||||
public HumanizeJson(object obj, int indent)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_indent = indent;
|
||||
_indentStr = new string(' ', indent * 4);
|
||||
_obj = obj;
|
||||
|
||||
ParseObject();
|
||||
}
|
||||
|
||||
public string GetResult() => _builder == null ? string.Empty : _builder.ToString().TrimEnd();
|
||||
|
||||
private void ParseObject()
|
||||
{
|
||||
switch (_obj)
|
||||
{
|
||||
case Dictionary<string, object> dictionary:
|
||||
AppendDictionary(dictionary);
|
||||
break;
|
||||
case List<object> list:
|
||||
AppendList(list);
|
||||
break;
|
||||
default:
|
||||
AppendString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendDictionary(Dictionary<string, object> objects)
|
||||
{
|
||||
foreach (var kvp in objects)
|
||||
{
|
||||
if (kvp.Value == null) continue;
|
||||
|
||||
var writeOutput = false;
|
||||
|
||||
switch (kvp.Value)
|
||||
{
|
||||
case Dictionary<string, object> valueDictionary:
|
||||
if (valueDictionary.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}{kvp.Key,-16}: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<object> valueList:
|
||||
if (valueList.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}{kvp.Key,-16}: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_builder.Append($"{_indentStr}{kvp.Key,-16}: ");
|
||||
break;
|
||||
}
|
||||
|
||||
if (writeOutput)
|
||||
_builder.AppendLine(new HumanizeJson(kvp.Value, _indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendList(List<object> objects)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var value in objects)
|
||||
{
|
||||
var writeOutput = false;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case Dictionary<string, object> valueDictionary:
|
||||
if (valueDictionary.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}[{index}]: object")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
case List<object> valueList:
|
||||
if (valueList.Count > 0)
|
||||
{
|
||||
writeOutput = true;
|
||||
_builder
|
||||
.Append($"{_indentStr}[{index}]: array[{valueList.Count}]")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
writeOutput = true;
|
||||
_builder.Append($"{_indentStr}[{index}]: ");
|
||||
break;
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
if (writeOutput)
|
||||
_builder.AppendLine(new HumanizeJson(value, _indent + 1).GetResult());
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendString()
|
||||
{
|
||||
var stringValue = _obj.ToString();
|
||||
|
||||
if (stringValue.Length + _indentStr.Length > 96 || stringValue.IndexOf('\r') >= 0 ||
|
||||
stringValue.IndexOf('\n') >= 0)
|
||||
{
|
||||
_builder.AppendLine();
|
||||
var stringLines = stringValue.ToLines().Select(l => l.Trim());
|
||||
|
||||
foreach (var line in stringLines)
|
||||
{
|
||||
_builder.AppendLine($"{_indentStr}{line}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_builder.Append($"{stringValue}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
335
Unosquare.Swan.Lite/Formatters/Json.Converter.cs
Normal file
335
Unosquare.Swan.Lite/Formatters/Json.Converter.cs
Normal file
@ -0,0 +1,335 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public static partial class Json
|
||||
{
|
||||
private class Converter
|
||||
{
|
||||
private static readonly ConcurrentDictionary<MemberInfo, string> MemberInfoNameCache =
|
||||
new ConcurrentDictionary<MemberInfo, string>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, Type> ListAddMethodCache = new ConcurrentDictionary<Type, Type>();
|
||||
|
||||
private readonly object _target;
|
||||
private readonly Type _targetType;
|
||||
private readonly bool _includeNonPublic;
|
||||
|
||||
private Converter(
|
||||
object source,
|
||||
Type targetType,
|
||||
ref object targetInstance,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
_targetType = targetInstance != null ? targetInstance.GetType() : targetType;
|
||||
_includeNonPublic = includeNonPublic;
|
||||
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sourceType = source.GetType();
|
||||
|
||||
if (_targetType == null || _targetType == typeof(object)) _targetType = sourceType;
|
||||
if (sourceType == _targetType)
|
||||
{
|
||||
_target = source;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TrySetInstance(targetInstance, source, ref _target))
|
||||
return;
|
||||
|
||||
ResolveObject(source, ref _target);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a json deserialized object (simple type, dictionary or list) to a new instance of the specified target type.
|
||||
/// </summary>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="targetType">Type of the target.</param>
|
||||
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
|
||||
/// <returns>The target object.</returns>
|
||||
internal static object FromJsonResult(object source,
|
||||
Type targetType,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
object nullRef = null;
|
||||
return new Converter(source, targetType, ref nullRef, includeNonPublic).GetResult();
|
||||
}
|
||||
|
||||
private static object FromJsonResult(object source,
|
||||
Type targetType,
|
||||
ref object targetInstance,
|
||||
bool includeNonPublic)
|
||||
{
|
||||
return new Converter(source, targetType, ref targetInstance, includeNonPublic).GetResult();
|
||||
}
|
||||
|
||||
private static Type GetAddMethodParameterType(Type targetType)
|
||||
=> ListAddMethodCache.GetOrAdd(targetType,
|
||||
x => x.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 1)?
|
||||
.GetParameters()[0]
|
||||
.ParameterType);
|
||||
|
||||
private static void GetByteArray(string sourceString, ref object target)
|
||||
{
|
||||
try
|
||||
{
|
||||
target = Convert.FromBase64String(sourceString);
|
||||
} // Try conversion from Base 64
|
||||
catch
|
||||
{
|
||||
target = Encoding.UTF8.GetBytes(sourceString);
|
||||
} // Get the string bytes in UTF8
|
||||
}
|
||||
|
||||
private static object GetSourcePropertyValue(IDictionary<string, object> sourceProperties,
|
||||
MemberInfo targetProperty)
|
||||
{
|
||||
var targetPropertyName = MemberInfoNameCache.GetOrAdd(
|
||||
targetProperty,
|
||||
x => Runtime.AttributeCache.RetrieveOne<JsonPropertyAttribute>(x)?.PropertyName ?? x.Name);
|
||||
|
||||
return sourceProperties.GetValueOrDefault(targetPropertyName);
|
||||
}
|
||||
|
||||
private bool TrySetInstance(object targetInstance, object source, ref object target)
|
||||
{
|
||||
if (targetInstance == null)
|
||||
{
|
||||
// Try to create a default instance
|
||||
try
|
||||
{
|
||||
source.CreateTarget(_targetType, _includeNonPublic, ref target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target = targetInstance;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private object GetResult() => _target ?? _targetType.GetDefault();
|
||||
|
||||
private void ResolveObject(object source, ref object target)
|
||||
{
|
||||
switch (source)
|
||||
{
|
||||
// Case 0: Special Cases Handling (Source and Target are of specific convertible types)
|
||||
// Case 0.1: Source is string, Target is byte[]
|
||||
case string sourceString when _targetType == typeof(byte[]):
|
||||
GetByteArray(sourceString, ref target);
|
||||
break;
|
||||
|
||||
// Case 1.1: Source is Dictionary, Target is IDictionary
|
||||
case Dictionary<string, object> sourceProperties when target is IDictionary targetDictionary:
|
||||
PopulateDictionary(sourceProperties, targetDictionary);
|
||||
break;
|
||||
|
||||
// Case 1.2: Source is Dictionary, Target is not IDictionary (i.e. it is a complex type)
|
||||
case Dictionary<string, object> sourceProperties:
|
||||
PopulateObject(sourceProperties);
|
||||
break;
|
||||
|
||||
// Case 2.1: Source is List, Target is Array
|
||||
case List<object> sourceList when target is Array targetArray:
|
||||
PopulateArray(sourceList, targetArray);
|
||||
break;
|
||||
|
||||
// Case 2.2: Source is List, Target is IList
|
||||
case List<object> sourceList when target is IList targetList:
|
||||
PopulateIList(sourceList, targetList);
|
||||
break;
|
||||
|
||||
// Case 3: Source is a simple type; Attempt conversion
|
||||
default:
|
||||
var sourceStringValue = source.ToStringInvariant();
|
||||
|
||||
// Handle basic types or enumerations if not
|
||||
if (!_targetType.TryParseBasicType(sourceStringValue, out target))
|
||||
GetEnumValue(sourceStringValue, ref target);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateIList(IList<object> objects, IList list)
|
||||
{
|
||||
var parameterType = GetAddMethodParameterType(_targetType);
|
||||
if (parameterType == null) return;
|
||||
|
||||
foreach (var item in objects)
|
||||
{
|
||||
try
|
||||
{
|
||||
list.Add(FromJsonResult(
|
||||
item,
|
||||
parameterType,
|
||||
_includeNonPublic));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateArray(IList<object> objects, Array array)
|
||||
{
|
||||
var elementType = _targetType.GetElementType();
|
||||
|
||||
for (var i = 0; i < objects.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetItem = FromJsonResult(
|
||||
objects[i],
|
||||
elementType,
|
||||
_includeNonPublic);
|
||||
array.SetValue(targetItem, i);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetEnumValue(string sourceStringValue, ref object target)
|
||||
{
|
||||
var enumType = Nullable.GetUnderlyingType(_targetType);
|
||||
if (enumType == null && _targetType.GetTypeInfo().IsEnum) enumType = _targetType;
|
||||
if (enumType == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
target = Enum.Parse(enumType, sourceStringValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateDictionary(IDictionary<string, object> sourceProperties, IDictionary targetDictionary)
|
||||
{
|
||||
// find the add method of the target dictionary
|
||||
var addMethod = _targetType.GetMethods()
|
||||
.FirstOrDefault(
|
||||
m => m.Name.Equals(AddMethodName) && m.IsPublic && m.GetParameters().Length == 2);
|
||||
|
||||
// skip if we don't have a compatible add method
|
||||
if (addMethod == null) return;
|
||||
var addMethodParameters = addMethod.GetParameters();
|
||||
if (addMethodParameters[0].ParameterType != typeof(string)) return;
|
||||
|
||||
// Retrieve the target entry type
|
||||
var targetEntryType = addMethodParameters[1].ParameterType;
|
||||
|
||||
// Add the items to the target dictionary
|
||||
foreach (var sourceProperty in sourceProperties)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetEntryValue = FromJsonResult(
|
||||
sourceProperty.Value,
|
||||
targetEntryType,
|
||||
_includeNonPublic);
|
||||
targetDictionary.Add(sourceProperty.Key, targetEntryValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateObject(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
if (_targetType.IsValueType())
|
||||
{
|
||||
PopulateFields(sourceProperties);
|
||||
}
|
||||
|
||||
PopulateProperties(sourceProperties);
|
||||
}
|
||||
|
||||
private void PopulateProperties(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
var properties = PropertyTypeCache.RetrieveFilteredProperties(_targetType, false, p => p.CanWrite);
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, property);
|
||||
if (sourcePropertyValue == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var currentPropertyValue = !property.PropertyType.IsArray
|
||||
? property.GetCacheGetMethod(_includeNonPublic)(_target)
|
||||
: null;
|
||||
|
||||
var targetPropertyValue = FromJsonResult(
|
||||
sourcePropertyValue,
|
||||
property.PropertyType,
|
||||
ref currentPropertyValue,
|
||||
_includeNonPublic);
|
||||
|
||||
property.GetCacheSetMethod(_includeNonPublic)(_target, new[] { targetPropertyValue });
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateFields(IDictionary<string, object> sourceProperties)
|
||||
{
|
||||
foreach (var field in FieldTypeCache.RetrieveAllFields(_targetType))
|
||||
{
|
||||
var sourcePropertyValue = GetSourcePropertyValue(sourceProperties, field);
|
||||
if (sourcePropertyValue == null) continue;
|
||||
|
||||
var targetPropertyValue = FromJsonResult(
|
||||
sourcePropertyValue,
|
||||
field.FieldType,
|
||||
_includeNonPublic);
|
||||
|
||||
try
|
||||
{
|
||||
field.SetValue(_target, targetPropertyValue);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
374
Unosquare.Swan.Lite/Formatters/Json.Deserializer.cs
Normal file
374
Unosquare.Swan.Lite/Formatters/Json.Deserializer.cs
Normal file
@ -0,0 +1,374 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple JSON Deserializer.
|
||||
/// </summary>
|
||||
private class Deserializer
|
||||
{
|
||||
#region State Variables
|
||||
|
||||
private readonly object _result;
|
||||
private readonly Dictionary<string, object> _resultObject;
|
||||
private readonly List<object> _resultArray;
|
||||
|
||||
private readonly ReadState _state = ReadState.WaitingForRootOpen;
|
||||
private readonly string _currentFieldName;
|
||||
private readonly string _json;
|
||||
|
||||
private int _index;
|
||||
|
||||
#endregion
|
||||
|
||||
private Deserializer(string json, int startIndex)
|
||||
{
|
||||
_json = json;
|
||||
|
||||
for (_index = startIndex; _index < _json.Length; _index++)
|
||||
{
|
||||
#region Wait for { or [
|
||||
|
||||
if (_state == ReadState.WaitingForRootOpen)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] == OpenObjectChar)
|
||||
{
|
||||
_resultObject = new Dictionary<string, object>();
|
||||
_state = ReadState.WaitingForField;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_json[_index] == OpenArrayChar)
|
||||
{
|
||||
_resultArray = new List<object>();
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw CreateParserException($"'{OpenObjectChar}' or '{OpenArrayChar}'");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for opening field " (only applies for object results)
|
||||
|
||||
if (_state == ReadState.WaitingForField)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_json[_index] != StringQuotedChar)
|
||||
throw CreateParserException($"'{StringQuotedChar}'");
|
||||
|
||||
var charCount = GetFieldNameCount();
|
||||
|
||||
_currentFieldName = Unescape(_json.SliceLength(_index + 1, charCount));
|
||||
_index += charCount + 1;
|
||||
_state = ReadState.WaitingForColon;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for field-value separator : (only applies for object results
|
||||
|
||||
if (_state == ReadState.WaitingForColon)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] != ValueSeparatorChar)
|
||||
throw CreateParserException($"'{ValueSeparatorChar}'");
|
||||
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for and Parse the value
|
||||
|
||||
if (_state == ReadState.WaitingForValue)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
// Handle empty arrays and empty objects
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
// determine the value based on what it starts with
|
||||
switch (_json[_index])
|
||||
{
|
||||
case StringQuotedChar: // expect a string
|
||||
ExtractStringQuoted();
|
||||
break;
|
||||
|
||||
case OpenObjectChar: // expect object
|
||||
case OpenArrayChar: // expect array
|
||||
ExtractObject();
|
||||
break;
|
||||
|
||||
case 't': // expect true
|
||||
ExtractConstant(TrueLiteral, true);
|
||||
break;
|
||||
|
||||
case 'f': // expect false
|
||||
ExtractConstant(FalseLiteral, false);
|
||||
break;
|
||||
|
||||
case 'n': // expect null
|
||||
ExtractConstant(NullLiteral, null);
|
||||
break;
|
||||
|
||||
default: // expect number
|
||||
ExtractNumber();
|
||||
break;
|
||||
}
|
||||
|
||||
_currentFieldName = null;
|
||||
_state = ReadState.WaitingForNextOrRootClose;
|
||||
continue;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Wait for closing ], } or an additional field or value ,
|
||||
|
||||
if (_state != ReadState.WaitingForNextOrRootClose) continue;
|
||||
|
||||
if (char.IsWhiteSpace(_json, _index)) continue;
|
||||
|
||||
if (_json[_index] == FieldSeparatorChar)
|
||||
{
|
||||
if (_resultObject != null)
|
||||
{
|
||||
_state = ReadState.WaitingForField;
|
||||
_currentFieldName = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
_state = ReadState.WaitingForValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((_resultObject != null && _json[_index] == CloseObjectChar) ||
|
||||
(_resultArray != null && _json[_index] == CloseArrayChar))
|
||||
{
|
||||
_result = _resultObject ?? _resultArray as object;
|
||||
return;
|
||||
}
|
||||
|
||||
throw CreateParserException($"'{FieldSeparatorChar}' '{CloseObjectChar}' or '{CloseArrayChar}'");
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
internal static object DeserializeInternal(string json) => new Deserializer(json, 0)._result;
|
||||
|
||||
private static string Unescape(string str)
|
||||
{
|
||||
// check if we need to unescape at all
|
||||
if (str.IndexOf(StringEscapeChar) < 0)
|
||||
return str;
|
||||
|
||||
var builder = new StringBuilder(str.Length);
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (str[i] != StringEscapeChar)
|
||||
{
|
||||
builder.Append(str[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i + 1 > str.Length - 1)
|
||||
break;
|
||||
|
||||
// escape sequence begins here
|
||||
switch (str[i + 1])
|
||||
{
|
||||
case 'u':
|
||||
i = ExtractEscapeSequence(str, i, builder);
|
||||
break;
|
||||
case 'b':
|
||||
builder.Append('\b');
|
||||
i += 1;
|
||||
break;
|
||||
case 't':
|
||||
builder.Append('\t');
|
||||
i += 1;
|
||||
break;
|
||||
case 'n':
|
||||
builder.Append('\n');
|
||||
i += 1;
|
||||
break;
|
||||
case 'f':
|
||||
builder.Append('\f');
|
||||
i += 1;
|
||||
break;
|
||||
case 'r':
|
||||
builder.Append('\r');
|
||||
i += 1;
|
||||
break;
|
||||
default:
|
||||
builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static int ExtractEscapeSequence(string str, int i, StringBuilder builder)
|
||||
{
|
||||
var startIndex = i + 2;
|
||||
var endIndex = i + 5;
|
||||
if (endIndex > str.Length - 1)
|
||||
{
|
||||
builder.Append(str[i + 1]);
|
||||
i += 1;
|
||||
return i;
|
||||
}
|
||||
|
||||
var hexCode = str.Slice(startIndex, endIndex).ConvertHexadecimalToBytes();
|
||||
builder.Append(Encoding.BigEndianUnicode.GetChars(hexCode));
|
||||
i += 5;
|
||||
return i;
|
||||
}
|
||||
|
||||
private int GetFieldNameCount()
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = _index + 1; j < _json.Length; j++)
|
||||
{
|
||||
if (_json[j] == StringQuotedChar && _json[j - 1] != StringEscapeChar)
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
return charCount;
|
||||
}
|
||||
|
||||
private void ExtractObject()
|
||||
{
|
||||
// Extract and set the value
|
||||
var deserializer = new Deserializer(_json, _index);
|
||||
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = deserializer._result;
|
||||
else
|
||||
_resultArray.Add(deserializer._result);
|
||||
|
||||
_index = deserializer._index;
|
||||
}
|
||||
|
||||
private void ExtractNumber()
|
||||
{
|
||||
var charCount = 0;
|
||||
for (var j = _index; j < _json.Length; j++)
|
||||
{
|
||||
if (char.IsWhiteSpace(_json[j]) || _json[j] == FieldSeparatorChar
|
||||
|| (_resultObject != null && _json[j] == CloseObjectChar)
|
||||
|| (_resultArray != null && _json[j] == CloseArrayChar))
|
||||
break;
|
||||
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var stringValue = _json.SliceLength(_index, charCount);
|
||||
|
||||
if (decimal.TryParse(stringValue, out var value) == false)
|
||||
throw CreateParserException("[number]");
|
||||
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += charCount - 1;
|
||||
}
|
||||
|
||||
private void ExtractConstant(string boolValue, bool? value)
|
||||
{
|
||||
if (!_json.SliceLength(_index, boolValue.Length).Equals(boolValue))
|
||||
throw CreateParserException($"'{ValueSeparatorChar}'");
|
||||
|
||||
// Extract and set the value
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += boolValue.Length - 1;
|
||||
}
|
||||
|
||||
private void ExtractStringQuoted()
|
||||
{
|
||||
var charCount = 0;
|
||||
var escapeCharFound = false;
|
||||
for (var j = _index + 1; j < _json.Length; j++)
|
||||
{
|
||||
if (_json[j] == StringQuotedChar && !escapeCharFound)
|
||||
break;
|
||||
|
||||
escapeCharFound = _json[j] == StringEscapeChar && !escapeCharFound;
|
||||
charCount++;
|
||||
}
|
||||
|
||||
// Extract and set the value
|
||||
var value = Unescape(_json.SliceLength(_index + 1, charCount));
|
||||
if (_currentFieldName != null)
|
||||
_resultObject[_currentFieldName] = value;
|
||||
else
|
||||
_resultArray.Add(value);
|
||||
|
||||
_index += charCount + 1;
|
||||
}
|
||||
|
||||
private FormatException CreateParserException(string expected)
|
||||
{
|
||||
var textPosition = _json.TextPositionAt(_index);
|
||||
return new FormatException(
|
||||
$"Parser error (Line {textPosition.Item1}, Col {textPosition.Item2}, State {_state}): Expected {expected} but got '{_json[_index]}'.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the different JSON read states.
|
||||
/// </summary>
|
||||
private enum ReadState
|
||||
{
|
||||
WaitingForRootOpen,
|
||||
WaitingForField,
|
||||
WaitingForColon,
|
||||
WaitingForValue,
|
||||
WaitingForNextOrRootClose,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
359
Unosquare.Swan.Lite/Formatters/Json.Serializer.cs
Normal file
359
Unosquare.Swan.Lite/Formatters/Json.Serializer.cs
Normal file
@ -0,0 +1,359 @@
|
||||
namespace Unosquare.Swan.Formatters
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// A very simple, light-weight JSON library written by Mario
|
||||
/// to teach Geo how things are done
|
||||
///
|
||||
/// This is an useful helper for small tasks but it doesn't represent a full-featured
|
||||
/// serializer such as the beloved Json.NET.
|
||||
/// </summary>
|
||||
public partial class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple JSON serializer.
|
||||
/// </summary>
|
||||
private class Serializer
|
||||
{
|
||||
#region Private Declarations
|
||||
|
||||
private static readonly Dictionary<int, string> IndentStrings = new Dictionary<int, string>();
|
||||
|
||||
private readonly SerializerOptions _options;
|
||||
private readonly string _result;
|
||||
private readonly StringBuilder _builder;
|
||||
private readonly string _lastCommaSearch;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Serializer" /> class.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object.</param>
|
||||
/// <param name="depth">The depth.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
private Serializer(object obj, int depth, SerializerOptions options)
|
||||
{
|
||||
if (depth > 20)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The max depth (20) has been reached. Serializer can not continue.");
|
||||
}
|
||||
|
||||
// Basic Type Handling (nulls, strings, number, date and bool)
|
||||
_result = ResolveBasicType(obj);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_result) == false)
|
||||
return;
|
||||
|
||||
_options = options;
|
||||
_lastCommaSearch = FieldSeparatorChar + (_options.Format ? Environment.NewLine : string.Empty);
|
||||
|
||||
// Handle circular references correctly and avoid them
|
||||
if (options.IsObjectPresent(obj))
|
||||
{
|
||||
_result = $"{{ \"$circref\": \"{Escape(obj.GetHashCode().ToStringInvariant(), false)}\" }}";
|
||||
return;
|
||||
}
|
||||
|
||||
// At this point, we will need to construct the object with a StringBuilder.
|
||||
_builder = new StringBuilder();
|
||||
|
||||
switch (obj)
|
||||
{
|
||||
case IDictionary itemsZero when itemsZero.Count == 0:
|
||||
_result = EmptyObjectLiteral;
|
||||
break;
|
||||
case IDictionary items:
|
||||
_result = ResolveDictionary(items, depth);
|
||||
break;
|
||||
case IEnumerable enumerableZero when !enumerableZero.Cast<object>().Any():
|
||||
_result = EmptyArrayLiteral;
|
||||
break;
|
||||
case IEnumerable enumerableBytes when enumerableBytes is byte[] bytes:
|
||||
_result = Serialize(bytes.ToBase64(), depth, _options);
|
||||
break;
|
||||
case IEnumerable enumerable:
|
||||
_result = ResolveEnumerable(enumerable, depth);
|
||||
break;
|
||||
default:
|
||||
_result = ResolveObject(obj, depth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal static string Serialize(object obj, int depth, SerializerOptions options)
|
||||
{
|
||||
return new Serializer(obj, depth, options)._result;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static string ResolveBasicType(object obj)
|
||||
{
|
||||
switch (obj)
|
||||
{
|
||||
case null:
|
||||
return NullLiteral;
|
||||
case string s:
|
||||
return Escape(s, true);
|
||||
case bool b:
|
||||
return b ? TrueLiteral : FalseLiteral;
|
||||
case Type _:
|
||||
case Assembly _:
|
||||
case MethodInfo _:
|
||||
case PropertyInfo _:
|
||||
case EventInfo _:
|
||||
return Escape(obj.ToString(), true);
|
||||
case DateTime d:
|
||||
return $"{StringQuotedChar}{d:s}{StringQuotedChar}";
|
||||
default:
|
||||
var targetType = obj.GetType();
|
||||
|
||||
if (!Definitions.BasicTypesInfo.ContainsKey(targetType))
|
||||
return string.Empty;
|
||||
|
||||
var escapedValue = Escape(Definitions.BasicTypesInfo[targetType].ToStringInvariant(obj), false);
|
||||
|
||||
return decimal.TryParse(escapedValue, out _)
|
||||
? $"{escapedValue}"
|
||||
: $"{StringQuotedChar}{escapedValue}{StringQuotedChar}";
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNonEmptyJsonArrayOrObject(string serialized)
|
||||
{
|
||||
if (serialized.Equals(EmptyObjectLiteral) || serialized.Equals(EmptyArrayLiteral)) return false;
|
||||
|
||||
// find the first position the character is not a space
|
||||
return serialized.Where(c => c != ' ').Select(c => c == OpenObjectChar || c == OpenArrayChar).FirstOrDefault();
|
||||
}
|
||||
|
||||
private static string Escape(string str, bool quoted)
|
||||
{
|
||||
if (str == null)
|
||||
return string.Empty;
|
||||
|
||||
var builder = new StringBuilder(str.Length * 2);
|
||||
if (quoted) builder.Append(StringQuotedChar);
|
||||
Escape(str, builder);
|
||||
if (quoted) builder.Append(StringQuotedChar);
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static void Escape(string str, StringBuilder builder)
|
||||
{
|
||||
foreach (var currentChar in str)
|
||||
{
|
||||
switch (currentChar)
|
||||
{
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/':
|
||||
builder
|
||||
.Append('\\')
|
||||
.Append(currentChar);
|
||||
break;
|
||||
case '\b':
|
||||
builder.Append("\\b");
|
||||
break;
|
||||
case '\t':
|
||||
builder.Append("\\t");
|
||||
break;
|
||||
case '\n':
|
||||
builder.Append("\\n");
|
||||
break;
|
||||
case '\f':
|
||||
builder.Append("\\f");
|
||||
break;
|
||||
case '\r':
|
||||
builder.Append("\\r");
|
||||
break;
|
||||
default:
|
||||
if (currentChar < ' ')
|
||||
{
|
||||
var escapeBytes = BitConverter.GetBytes((ushort)currentChar);
|
||||
if (BitConverter.IsLittleEndian == false)
|
||||
Array.Reverse(escapeBytes);
|
||||
|
||||
builder.Append("\\u")
|
||||
.Append(escapeBytes[1].ToString("X").PadLeft(2, '0'))
|
||||
.Append(escapeBytes[0].ToString("X").PadLeft(2, '0'));
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(currentChar);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, object> CreateDictionary(
|
||||
Dictionary<string, MemberInfo> fields,
|
||||
string targetType,
|
||||
object target)
|
||||
{
|
||||
// Create the dictionary and extract the properties
|
||||
var objectDictionary = new Dictionary<string, object>();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_options.TypeSpecifier) == false)
|
||||
objectDictionary[_options.TypeSpecifier] = targetType;
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// Build the dictionary using property names and values
|
||||
// Note: used to be: property.GetValue(target); but we would be reading private properties
|
||||
try
|
||||
{
|
||||
objectDictionary[field.Key] = field.Value is PropertyInfo property
|
||||
? property.GetCacheGetMethod(_options.IncludeNonPublic)(target)
|
||||
: (field.Value as FieldInfo)?.GetValue(target);
|
||||
}
|
||||
catch
|
||||
{
|
||||
/* ignored */
|
||||
}
|
||||
}
|
||||
|
||||
return objectDictionary;
|
||||
}
|
||||
|
||||
private string ResolveDictionary(IDictionary items, int depth)
|
||||
{
|
||||
Append(OpenObjectChar, depth);
|
||||
AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
var writeCount = 0;
|
||||
foreach (var key in items.Keys)
|
||||
{
|
||||
// Serialize and append the key (first char indented)
|
||||
Append(StringQuotedChar, depth + 1);
|
||||
Escape(key.ToString(), _builder);
|
||||
_builder
|
||||
.Append(StringQuotedChar)
|
||||
.Append(ValueSeparatorChar)
|
||||
.Append(" ");
|
||||
|
||||
// Serialize and append the value
|
||||
var serializedValue = Serialize(items[key], depth + 1, _options);
|
||||
|
||||
if (IsNonEmptyJsonArrayOrObject(serializedValue)) AppendLine();
|
||||
Append(serializedValue, 0);
|
||||
|
||||
// Add a comma and start a new line -- We will remove the last one when we are done writing the elements
|
||||
Append(FieldSeparatorChar, 0);
|
||||
AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the object and set the result
|
||||
RemoveLastComma();
|
||||
Append(CloseObjectChar, writeCount > 0 ? depth : 0);
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private string ResolveObject(object target, int depth)
|
||||
{
|
||||
var targetType = target.GetType();
|
||||
var fields = _options.GetProperties(targetType);
|
||||
|
||||
if (fields.Count == 0 && string.IsNullOrWhiteSpace(_options.TypeSpecifier))
|
||||
return EmptyObjectLiteral;
|
||||
|
||||
// If we arrive here, then we convert the object into a
|
||||
// dictionary of property names and values and call the serialization
|
||||
// function again
|
||||
var objectDictionary = CreateDictionary(fields, targetType.ToString(), target);
|
||||
|
||||
return Serialize(objectDictionary, depth, _options);
|
||||
}
|
||||
|
||||
private string ResolveEnumerable(IEnumerable target, int depth)
|
||||
{
|
||||
// Cast the items as a generic object array
|
||||
var items = target.Cast<object>();
|
||||
|
||||
Append(OpenArrayChar, depth);
|
||||
AppendLine();
|
||||
|
||||
// Iterate through the elements and output recursively
|
||||
var writeCount = 0;
|
||||
foreach (var entry in items)
|
||||
{
|
||||
var serializedValue = Serialize(entry, depth + 1, _options);
|
||||
|
||||
if (IsNonEmptyJsonArrayOrObject(serializedValue))
|
||||
Append(serializedValue, 0);
|
||||
else
|
||||
Append(serializedValue, depth + 1);
|
||||
|
||||
Append(FieldSeparatorChar, 0);
|
||||
AppendLine();
|
||||
writeCount++;
|
||||
}
|
||||
|
||||
// Output the end of the array and set the result
|
||||
RemoveLastComma();
|
||||
Append(CloseArrayChar, writeCount > 0 ? depth : 0);
|
||||
return _builder.ToString();
|
||||
}
|
||||
|
||||
private void SetIndent(int depth)
|
||||
{
|
||||
if (_options.Format == false || depth <= 0) return;
|
||||
|
||||
_builder.Append(IndentStrings.GetOrAdd(depth, x => new string(' ', x * 4)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the last comma in the current string builder.
|
||||
/// </summary>
|
||||
private void RemoveLastComma()
|
||||
{
|
||||
if (_builder.Length < _lastCommaSearch.Length)
|
||||
return;
|
||||
|
||||
if (_lastCommaSearch.Where((t, i) => _builder[_builder.Length - _lastCommaSearch.Length + i] != t).Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got this far, we simply remove the comma character
|
||||
_builder.Remove(_builder.Length - _lastCommaSearch.Length, 1);
|
||||
}
|
||||
|
||||
private void Append(string text, int depth)
|
||||
{
|
||||
SetIndent(depth);
|
||||
_builder.Append(text);
|
||||
}
|
||||
|
||||
private void Append(char text, int depth)
|
||||
{
|
||||
SetIndent(depth);
|
||||
_builder.Append(text);
|
||||
}
|
||||
|
||||
private void AppendLine()
|
||||
{
|
||||
if (_options.Format == false) return;
|
||||
_builder.Append(Environment.NewLine);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user