diff --git a/Samples/Media SDK/MediaPlayerSample/MediaPlayerSample.cs b/Samples/Media SDK/MediaPlayerSample/MediaPlayerSample.cs index e5e7e021..6661d86c 100644 --- a/Samples/Media SDK/MediaPlayerSample/MediaPlayerSample.cs +++ b/Samples/Media SDK/MediaPlayerSample/MediaPlayerSample.cs @@ -208,7 +208,7 @@ void DecreasePlaySpeed() void OpenFile() { - OpenFileDialog openFileDialog = new() { Filter = "Video files (*.mp4;*..g64;*..g64x)|*.mp4;*.g64;*..g64x|All files (*.*)|*.*" }; + OpenFileDialog openFileDialog = new() { Filter = "Video files (*.mp4;*.g64;*.g64x)|*.mp4;*.g64;*.g64x|All files (*.*)|*.*" }; if (openFileDialog.ShowDialog().GetValueOrDefault()) { player.OpenFile(openFileDialog.FileName); @@ -365,4 +365,4 @@ void DisplayControls() Console.WriteLine(" I: Toggle statistics overlay"); Console.WriteLine(); } -} \ No newline at end of file +} diff --git a/Samples/Media SDK/OverlaySample/OverlayExtensions.cs b/Samples/Media SDK/OverlaySample/OverlayExtensions.cs index 31ef9d9c..982ce3ac 100644 --- a/Samples/Media SDK/OverlaySample/OverlayExtensions.cs +++ b/Samples/Media SDK/OverlaySample/OverlayExtensions.cs @@ -3,14 +3,15 @@ namespace Genetec.Dap.CodeSamples; +using System.Threading; using System.Threading.Tasks; using Sdk.Media.Overlay; public static class OverlayExtensions { - public static async Task WaitUntilReadyForUpdate(this Overlay overlay) + public static async Task WaitUntilReadyForUpdate(this Overlay overlay, CancellationToken cancellationToken = default) { - var completion = new TaskCompletionSource(); + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); overlay.StateChange += OnStateChange; try @@ -20,7 +21,10 @@ public static async Task WaitUntilReadyForUpdate(this Overlay overlay) return; } - await completion.Task; + using (cancellationToken.Register(() => completion.TrySetCanceled(cancellationToken))) + { + await completion.Task; + } } finally { @@ -31,8 +35,8 @@ void OnStateChange(object sender, OverlayStatusEventArgs e) { if (e.CanPropagateUpdate) { - completion.SetResult(null); + completion.TrySetResult(null); } } } -} \ No newline at end of file +} diff --git a/Samples/Media SDK/OverlaySample/OverlaySample.cs b/Samples/Media SDK/OverlaySample/OverlaySample.cs index 72390d07..976e791f 100644 --- a/Samples/Media SDK/OverlaySample/OverlaySample.cs +++ b/Samples/Media SDK/OverlaySample/OverlaySample.cs @@ -51,7 +51,7 @@ private async Task DrawBouncingBall(Guid cameraId, CancellationToken token) { const string layerId = "69A64ACE-6DDC-4142-AD04-06690D8591B3"; // Replace with a unique layer ID for your overlay. Layer ID must be unique and deterministic - Overlay overlay = await InitializeOverlay(cameraId, "Bouncing ball"); + Overlay overlay = await InitializeOverlay(cameraId, "Bouncing ball", token); Layer layer = overlay.CreateLayer(new Guid(layerId), "Bouncing ball"); var bouncingBall = new BouncingBall(50, 50, 50, 50, 25) { CanvasHeight = s_canvasHeight, CanvasWidth = s_canvasWidth }; @@ -73,7 +73,7 @@ private async Task DrawTimecode(Guid cameraId, CancellationToken token) { const string layerId = "92AEA5CA-E0F5-4122-872A-DB9A9F7437F7"; // Replace with a unique layer ID for your overlay. Layer ID must be unique and deterministic - Overlay overlay = await InitializeOverlay(cameraId, "Timecode"); + Overlay overlay = await InitializeOverlay(cameraId, "Timecode", token); Layer layer = overlay.CreateLayer(new Guid(layerId), "Timecode"); var timeDisplay = new TimeDisplay(); @@ -95,7 +95,7 @@ private async Task DrawRecordingStatus(Camera camera, CancellationToken token) { const string layerId = "A1B2C3D4-E5F6-7890-1234-567890ABCDEF"; // Replace with a unique layer ID for your overlay. Layer ID must be unique and deterministic - Overlay overlay = await InitializeOverlay(camera.Guid, "Recording Status"); + Overlay overlay = await InitializeOverlay(camera.Guid, "Recording Status", token); Layer layer = overlay.CreateLayer(new Guid(layerId), "Recording Status"); var recordingStatus = new RecordingStatus(camera); @@ -113,7 +113,7 @@ private async Task DrawRecordingStatus(Camera camera, CancellationToken token) } } - private async Task InitializeOverlay(Guid camera, string overlayName) + private async Task InitializeOverlay(Guid camera, string overlayName, CancellationToken token) { Overlay overlay = OverlayFactory.Get(camera, overlayName); @@ -122,7 +122,7 @@ private async Task InitializeOverlay(Guid camera, string overlayName) overlay.Initialize(s_canvasHeight, s_canvasWidth); } - await overlay.WaitUntilReadyForUpdate(); + await overlay.WaitUntilReadyForUpdate(token); return overlay; } -} \ No newline at end of file +} diff --git a/Samples/Media SDK/VideoSourceFilterSample/VideoSourceFilterSample.cs b/Samples/Media SDK/VideoSourceFilterSample/VideoSourceFilterSample.cs index e196a9dc..4cf9d05d 100644 --- a/Samples/Media SDK/VideoSourceFilterSample/VideoSourceFilterSample.cs +++ b/Samples/Media SDK/VideoSourceFilterSample/VideoSourceFilterSample.cs @@ -37,7 +37,7 @@ protected override async Task RunAsync(Engine engine, CancellationToken token) private async Task GetCameraSnapshot(Engine engine, Guid camera) { - var completion = new TaskCompletionSource(); + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using (var videoSourceFilter = new VideoSourceFilter()) { @@ -52,6 +52,7 @@ private async Task GetCameraSnapshot(Engine engine, Guid camera) finally { videoSourceFilter.FrameDecoded -= OnFrameDecoded; + videoSourceFilter.PlayerStateChanged -= OnPlayerStateChanged; videoSourceFilter.Stop(); } } @@ -75,7 +76,8 @@ void OnFrameDecoded(object sender, FrameDecodedEventArgs args) } catch (Exception ex) { - completion.SetException(ex); + completion.TrySetException(ex); + return; } memoryStream.Position = 0; @@ -100,4 +102,4 @@ void OnPlayerStateChanged(object sender, PlayerStateChangedEventArgs e) } } } -} \ No newline at end of file +} diff --git a/Samples/Platform SDK/AccessVerifierSample/AccessVerifierSample.cs b/Samples/Platform SDK/AccessVerifierSample/AccessVerifierSample.cs index 21f7119a..07e23cd6 100644 --- a/Samples/Platform SDK/AccessVerifierSample/AccessVerifierSample.cs +++ b/Samples/Platform SDK/AccessVerifierSample/AccessVerifierSample.cs @@ -18,9 +18,14 @@ public class AccessVerifierSample : SampleBase protected override async Task RunAsync(Engine engine, CancellationToken token) { // Load the specified entity types into the entity cache - await LoadEntities(engine, token, EntityType.Credential, EntityType.AccessPoint, EntityType.Schedule, EntityType.AccessRule); + await LoadEntities(engine, token, EntityType.Credential, EntityType.AccessPoint, EntityType.Schedule, EntityType.AccessRule, EntityType.Door); List doorGuids = engine.GetEntities(EntityType.Door).Select(door => door.Guid).ToList(); + if (doorGuids.Count == 0) + { + Console.WriteLine("No doors were found. The access matrix cannot be generated."); + return; + } DateTime currentTime = DateTime.UtcNow; @@ -63,4 +68,4 @@ static void GenerateAccessMatrixCsv(IEngine engine, ICollection<(AccessPoint Acc textWriter.WriteLine(); } } -} \ No newline at end of file +} diff --git a/Samples/Platform SDK/VideoUnitSample/VideoUnitSample.cs b/Samples/Platform SDK/VideoUnitSample/VideoUnitSample.cs index ebe20f07..e4775dd6 100644 --- a/Samples/Platform SDK/VideoUnitSample/VideoUnitSample.cs +++ b/Samples/Platform SDK/VideoUnitSample/VideoUnitSample.cs @@ -120,7 +120,7 @@ private void PrintProductCapability(ProductCapability capability) private async Task AddVideoUnit(IVideoUnitManager videoUnitManager, AddVideoUnitInfo videoUnitInfo, Guid archiver, IProgress progress = default) { - var completion = new TaskCompletionSource(); + var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); videoUnitManager.EnrollmentStatusChanged += OnEnrollmentStatusChanged; try @@ -136,19 +136,36 @@ private async Task AddVideoUnit(IVideoUnitManager videoUnitManager, AddVid void OnEnrollmentStatusChanged(object sender, UnitEnrolledEventArgs e) { + if (!IsEnrollmentForRequestedUnit(e)) + { + return; + } + progress?.Report(e.EnrollmentResult); if (e.EnrollmentResult != EnrollmentResult.Connecting) { if (e.Unit != Guid.Empty) { - completion.SetResult(e.Unit); + completion.TrySetResult(e.Unit); } else { - completion.SetException(new Exception($"Unable to enroll the video unit: {e.EnrollmentResult}")); + completion.TrySetException(new Exception($"Unable to enroll the video unit: {e.EnrollmentResult}")); } } } + + bool IsEnrollmentForRequestedUnit(UnitEnrolledEventArgs e) + { + string expectedAddress = videoUnitInfo.Hostname ?? videoUnitInfo.IPEndPoint?.Address.ToString(); + if (!string.IsNullOrWhiteSpace(expectedAddress) && !string.Equals(e.Address, expectedAddress, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + int expectedPort = videoUnitInfo.Port; + return expectedPort < 0 || e.Port == expectedPort; + } } -} \ No newline at end of file +} diff --git a/Samples/Plugin SDK/CustomReportSample/AsyncEnumerableExtensions.cs b/Samples/Plugin SDK/CustomReportSample/AsyncEnumerableExtensions.cs index c4e6fabc..76011d2a 100644 --- a/Samples/Plugin SDK/CustomReportSample/AsyncEnumerableExtensions.cs +++ b/Samples/Plugin SDK/CustomReportSample/AsyncEnumerableExtensions.cs @@ -3,9 +3,11 @@ namespace Genetec.Dap.CodeSamples; +using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using System.Threading.Tasks; public static class AsyncEnumerableExtensions { @@ -16,25 +18,30 @@ public static class AsyncEnumerableExtensions /// The source to buffer. /// The size of each buffer. /// The cancellation token. - /// An of representing the buffered elements. - public static async IAsyncEnumerable> Buffer(this IAsyncEnumerable source, int bufferSize, [EnumeratorCancellation] CancellationToken cancellationToken = default) + /// An of representing the buffered elements. + public static async IAsyncEnumerable> Buffer(this IAsyncEnumerable source, int bufferSize, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - await using IAsyncEnumerator enumerator = source.GetAsyncEnumerator(cancellationToken); - while (await enumerator.MoveNextAsync()) + if (bufferSize <= 0) { - yield return BufferInternal(); + throw new ArgumentOutOfRangeException(nameof(bufferSize)); } - async IAsyncEnumerable BufferInternal() + var batch = new List(bufferSize); + + await foreach (T item in source.WithCancellation(cancellationToken)) { - for (var i = 0; i < bufferSize; i++) - { - cancellationToken.ThrowIfCancellationRequested(); + batch.Add(item); - yield return enumerator.Current; - if (!await enumerator.MoveNextAsync()) - yield break; + if (batch.Count == bufferSize) + { + yield return batch; + batch = new List(bufferSize); } } + + if (batch.Count > 0) + { + yield return batch; + } } -} \ No newline at end of file +} diff --git a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ActivityTrailsReportHandler.cs b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ActivityTrailsReportHandler.cs index 47cb5818..6f9933b9 100644 --- a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ActivityTrailsReportHandler.cs +++ b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ActivityTrailsReportHandler.cs @@ -19,9 +19,9 @@ public ActivityTrailsReportHandler(IEngine engine, Role role) : base(engine, rol { } - protected override async Task ProcessBatch(DataTable table, IAsyncEnumerable batch) + protected override void ProcessBatch(DataTable table, IReadOnlyList batch) { - await foreach (ActivityTrailRow row in batch) + foreach (ActivityTrailRow row in batch) { table.AddIRow(row); } @@ -68,4 +68,4 @@ protected override async IAsyncEnumerable GetRecordsAsync(Acti .SetInitiatorApplication(ApplicationType.SecurityDesk, "Security Desk", Environment.MachineName); } } -} \ No newline at end of file +} diff --git a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/AuditTrailsReportHandler.cs b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/AuditTrailsReportHandler.cs index db047626..c0445ba5 100644 --- a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/AuditTrailsReportHandler.cs +++ b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/AuditTrailsReportHandler.cs @@ -19,9 +19,9 @@ public AuditTrailsReportHandler(IEngine engine, Role role) : base(engine, role) { } - protected override async Task ProcessBatch(DataTable table, IAsyncEnumerable batch) + protected override void ProcessBatch(DataTable table, IReadOnlyList batch) { - await foreach (AuditTrailRow row in batch) + foreach (AuditTrailRow row in batch) { table.AddIRow(row); } @@ -70,4 +70,4 @@ protected override async IAsyncEnumerable GetRecordsAsync(AuditTr .SetInitiatorApplication(ApplicationType.SecurityDesk, "Security Desk", Environment.MachineName); } } -} \ No newline at end of file +} diff --git a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ReportHandler.cs b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ReportHandler.cs index 28addf79..a8310938 100644 --- a/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ReportHandler.cs +++ b/Samples/Plugin SDK/CustomReportSample/Server/ReportHandlers/ReportHandler.cs @@ -45,12 +45,12 @@ public async Task HandleAsync(ReportQueryReceivedEventArgs args, Ca int totalSent = 0; int maxResults = args.Query.MaximumResultCount; - await foreach (IAsyncEnumerable batch in records.Buffer(GetBatchSize()).WithCancellation(token)) + await foreach (IReadOnlyList batch in records.Buffer(GetBatchSize()).WithCancellation(token)) { token.ThrowIfCancellationRequested(); DataTable table = CreateDataTable(query); - await ProcessBatch(table, batch); + ProcessBatch(table, batch); SendQueryResult(args, table); totalSent += table.Rows.Count; @@ -72,9 +72,9 @@ protected virtual DataTable CreateDataTable(TQuery query) return query.GetNewDataTables().First(); } - protected virtual async Task ProcessBatch(DataTable table, IAsyncEnumerable batch) + protected virtual void ProcessBatch(DataTable table, IReadOnlyList batch) { - await foreach (TRecord record in batch) + foreach (TRecord record in batch) { if (record is IRow row) { @@ -121,4 +121,4 @@ protected virtual void Dispose(bool disposing) Logger.Dispose(); } } -} \ No newline at end of file +} diff --git a/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs index fd3af069..2687e09e 100644 --- a/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs +++ b/Samples/Workspace SDK/TimelineProviderSample/AccessControlTimelineProvider.cs @@ -3,6 +3,7 @@ namespace Genetec.Dap.CodeSamples; +using Genetec.Sdk.Diagnostics.Logging.Core; using Sdk; using Sdk.Events.AccessPoint; using Sdk.Queries; @@ -17,37 +18,49 @@ namespace Genetec.Dap.CodeSamples; public class AccessControlTimelineProvider : TimelineProvider, IDisposable { + private readonly Logger m_logger; private readonly Workspace m_workspace; public AccessControlTimelineProvider(Workspace workspace) { + m_logger = Logger.CreateInstanceLogger(this); m_workspace = workspace; m_workspace.Sdk.EventReceived += OnEventReceived; } public override void Query(ContentGroup contentGroup, DateTime startTime, DateTime endTime) { - Task.Run(async () => + _ = Task.Run(async () => { - var query = (CardholderActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.CardholderActivity); - query.TimeRange.SetTimeRange(startTime, endTime); - query.Events.Clear(); - query.Events.Add(EventType.AccessGranted); - query.Events.Add(EventType.AccessRefused); + try + { + var query = (CardholderActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.CardholderActivity); + query.TimeRange.SetTimeRange(startTime, endTime); + query.Events.Clear(); + query.Events.Add(EventType.AccessGranted); + query.Events.Add(EventType.AccessRefused); - QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null); + QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null); - var events = result.Data.AsEnumerable().Select(row => - { - var timestamp = row.Field(AccessControlReportQuery.TimestampColumnName); - var cardholderGuid = row.Field(AccessControlReportQuery.CardholderGuidColumnName); - var eventType = row.Field(AccessControlReportQuery.EventTypeColumnName); + var events = result.Data.AsEnumerable().Select(row => + { + var timestamp = row.Field(AccessControlReportQuery.TimestampColumnName); + var cardholderGuid = row.Field(AccessControlReportQuery.CardholderGuidColumnName); + var eventType = row.Field(AccessControlReportQuery.EventTypeColumnName); - return new AccessTimelineEvent(cardholderGuid, timestamp, eventType == EventType.AccessGranted); - }); + return new AccessTimelineEvent(cardholderGuid, timestamp, eventType == EventType.AccessGranted); + }); - InsertEvents(events); - OnQueryCompleted(); + InsertEvents(events); + } + catch (Exception ex) + { + m_logger.TraceError(ex, $"Failed to query access control timeline events: {ex.Message}"); + } + finally + { + OnQueryCompleted(); + } }); } @@ -72,5 +85,6 @@ private void OnEventReceived(object sender, EventReceivedEventArgs e) public void Dispose() { m_workspace.Sdk.EventReceived -= OnEventReceived; + m_logger.Dispose(); } } diff --git a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs index c1189716..1a2ef737 100644 --- a/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs +++ b/Samples/Workspace SDK/TimelineProviderSample/AlarmTimelineProvider.cs @@ -3,6 +3,7 @@ namespace Genetec.Dap.CodeSamples; +using Genetec.Sdk.Diagnostics.Logging.Core; using System; using System.Data; using System.Linq; @@ -15,44 +16,57 @@ namespace Genetec.Dap.CodeSamples; public class AlarmTimelineProvider : TimelineProvider, IDisposable { + private readonly Logger m_logger; private readonly Workspace m_workspace; public AlarmTimelineProvider(Workspace workspace) { + m_logger = Logger.CreateInstanceLogger(this); m_workspace = workspace; m_workspace.Sdk.AlarmAcknowledged += OnAlarmAcknowledged; } public override void Query(ContentGroup contentGroup, DateTime startTime, DateTime endTime) { - Task.Run(async () => + _ = Task.Run(async () => { - var query = (AlarmActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.AlarmActivity); - query.TriggeredTimeRange.SetTimeRange(startTime, endTime); - query.States.Add(AlarmState.Active); - query.States.Add(AlarmState.AknowledgeRequired); - query.States.Add(AlarmState.SourceConditionInvestigating); + try + { + var query = (AlarmActivityQuery)m_workspace.Sdk.ReportManager.CreateReportQuery(ReportType.AlarmActivity); + query.TriggeredTimeRange.SetTimeRange(startTime, endTime); + query.States.Add(AlarmState.Active); + query.States.Add(AlarmState.AknowledgeRequired); + query.States.Add(AlarmState.SourceConditionInvestigating); - QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null); + QueryCompletedEventArgs result = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null); - var events = result.Data.AsEnumerable().Select(row => - { - var alarmGuid = row.Field(AlarmActivityQuery.AlarmColumnName); - var instanceId = row.Field(AlarmActivityQuery.InstanceIdColumnName); - var triggerTime = row.Field(AlarmActivityQuery.TriggerTimeColumnName); + var events = result.Data.AsEnumerable().Select(row => + { + var alarmGuid = row.Field(AlarmActivityQuery.AlarmColumnName); + var instanceId = row.Field(AlarmActivityQuery.InstanceIdColumnName); + var triggerTime = row.Field(AlarmActivityQuery.TriggerTimeColumnName); - return new AlarmTimelineEvent(alarmGuid, instanceId, triggerTime); - }); + return new AlarmTimelineEvent(alarmGuid, instanceId, triggerTime); + }); - InsertEvents(events); - OnQueryCompleted(); + InsertEvents(events); + } + catch (Exception ex) + { + m_logger.TraceError(ex, $"Failed to query alarm timeline events: {ex.Message}"); + } + finally + { + OnQueryCompleted(); + } }); } private void OnAlarmAcknowledged(object sender, AlarmAcknowledgedEventArgs e) { foreach (AlarmTimelineEvent timelineEvent in GetEvents().OfType() - .Where(t => t.AlarmGuid == e.AlarmGuid && t.InstanceId == e.InstanceId)) + .Where(t => t.AlarmGuid == e.AlarmGuid && t.InstanceId == e.InstanceId) + .ToList()) { RemoveEvent(timelineEvent); } @@ -61,5 +75,6 @@ private void OnAlarmAcknowledged(object sender, AlarmAcknowledgedEventArgs e) public void Dispose() { m_workspace.Sdk.AlarmAcknowledged -= OnAlarmAcknowledged; + m_logger.Dispose(); } }