/Horizon/MainForm.cs |
@@ -3,25 +3,33 @@ |
using System.Collections.Specialized; |
using System.ComponentModel; |
using System.Data.SQLite; |
using System.Diagnostics; |
using System.Drawing; |
using System.IO; |
using System.Linq; |
using System.Net; |
using System.Reflection; |
using System.Text; |
using System.Threading; |
using System.Threading.Tasks; |
using System.Threading.Tasks.Dataflow; |
using System.Windows.Forms; |
using Horizon.Database; |
using Horizon.Snapshots; |
using Horizon.Utilities; |
using Horizon.Utilities.Serialization; |
using Mono.Zeroconf; |
using NetSparkleUpdater; |
using NetSparkleUpdater.Enums; |
using NetSparkleUpdater.SignatureVerifiers; |
using NetSparkleUpdater.UI.WinForms; |
using Newtonsoft.Json; |
using Serilog; |
using TrackedFolders; |
using WatsonTcp; |
using static Horizon.Utilities.Networking.Miscellaneous; |
using CaptureMode = Configuration.CaptureMode; |
using Path = System.IO.Path; |
|
namespace Horizon |
{ |
@@ -69,6 +77,10 @@ |
|
private readonly LogMemorySink _memorySink; |
|
private RegisterService _horizonDiscoveryService; |
|
private WatsonTcpServer _horizonNetworkShare; |
|
public bool MemorySinkEnabled { get; set; } |
|
#endregion |
@@ -77,8 +89,6 @@ |
|
public MainForm(Mutex mutex) : this() |
{ |
InitializeComponent(); |
|
_memorySink = new LogMemorySink(); |
|
Log.Logger = new LoggerConfiguration() |
@@ -88,9 +98,10 @@ |
rollingInterval: RollingInterval.Day) |
.CreateLogger(); |
|
_snapshotDatabase = new SnapshotDatabase(); |
_snapshotDatabase = new SnapshotDatabase(_cancellationToken); |
_snapshotDatabase.SnapshotRevert += SnapshotDatabase_SnapshotRevert; |
_snapshotDatabase.SnapshotCreate += SnapshotDatabase_SnapshotCreate; |
_snapshotDatabase.SnapshotTransferReceived += SnapshotDatabase_SnapshotTransferReceived; |
|
TrackedFolders = new TrackedFolders.TrackedFolders(); |
TrackedFolders.Folder.CollectionChanged += Folder_CollectionChanged; |
@@ -111,6 +122,8 @@ |
|
public MainForm() |
{ |
InitializeComponent(); |
|
_cancellationTokenSource = new CancellationTokenSource(); |
_cancellationToken = _cancellationTokenSource.Token; |
|
@@ -138,6 +151,7 @@ |
|
_snapshotDatabase.SnapshotRevert -= SnapshotDatabase_SnapshotRevert; |
_snapshotDatabase.SnapshotCreate -= SnapshotDatabase_SnapshotCreate; |
_snapshotDatabase.SnapshotTransferReceived -= SnapshotDatabase_SnapshotTransferReceived; |
|
_snapshotDatabase.Dispose(); |
|
@@ -148,6 +162,107 @@ |
|
#region Event Handlers |
|
private void networkSharingToolStripMenuItem_CheckStateChanged(object sender, EventArgs e) |
{ |
var toolStripMenuItem = (ToolStripMenuItem)sender; |
|
switch (toolStripMenuItem.CheckState) |
{ |
case CheckState.Checked: |
var freePort = GetAvailableTcpPort(); |
|
_horizonNetworkShare = new WatsonTcpServer("0.0.0.0", freePort); |
_horizonNetworkShare.Events.ClientConnected += Events_ClientConnected; |
_horizonNetworkShare.Events.ClientDisconnected += Events_ClientDisconnected; |
_horizonNetworkShare.Events.MessageReceived += Events_MessageReceived; |
_horizonNetworkShare.Events.ExceptionEncountered += Events_ExceptionEncountered; |
#pragma warning disable CS4014 |
_horizonNetworkShare.Start(); |
#pragma warning restore CS4014 |
|
try |
{ |
_horizonDiscoveryService = new RegisterService(); |
_horizonDiscoveryService.Name = $"Horizon ({Environment.MachineName})"; |
_horizonDiscoveryService.RegType = "_horizon._tcp"; |
_horizonDiscoveryService.ReplyDomain = "local."; |
_horizonDiscoveryService.UPort = freePort; |
_horizonDiscoveryService.Register(); |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Service discovery protocol could not be stared."); |
} |
|
Configuration.NetworkSharing = true; |
break; |
case CheckState.Unchecked: |
if (_horizonNetworkShare != null) |
{ |
_horizonNetworkShare.Events.ClientConnected -= Events_ClientConnected; |
_horizonNetworkShare.Events.ClientDisconnected -= Events_ClientDisconnected; |
_horizonNetworkShare.Events.MessageReceived -= Events_MessageReceived; |
_horizonNetworkShare.Events.ExceptionEncountered -= Events_ExceptionEncountered; |
|
_horizonNetworkShare.Dispose(); |
_horizonNetworkShare = null; |
} |
|
if (_horizonDiscoveryService != null) |
{ |
_horizonDiscoveryService.Dispose(); |
_horizonDiscoveryService = null; |
|
} |
|
Configuration.NetworkSharing = false; |
break; |
} |
|
ChangedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), |
async () => { await SaveConfiguration(); }, _cancellationToken); |
} |
|
private void Events_ExceptionEncountered(object sender, ExceptionEventArgs e) |
{ |
Log.Error(e.Exception,$"Client threw exception."); |
} |
|
private async void Events_MessageReceived(object sender, MessageReceivedEventArgs e) |
{ |
Log.Information($"Client {e.Client?.IpPort} sent {e.Data?.Length} bytes via network sharing."); |
|
if (e.Data?.Length == 0) |
{ |
return; |
} |
|
try |
{ |
var payload = Encoding.UTF8.GetString(e.Data); |
|
var completeSnapshot = JsonConvert.DeserializeObject<TransferSnapshot>(payload); |
|
await _snapshotDatabase.ApplyTransferSnapshotAsync(completeSnapshot, _cancellationToken); |
|
Log.Information($"Stored {completeSnapshot.Name} from {e.Client?.IpPort}"); |
} |
catch (Exception exception) |
{ |
Log.Error(exception, $"Failed to process network share from {e.Client?.IpPort}."); |
} |
} |
|
private void Events_ClientDisconnected(object sender, WatsonTcp.DisconnectionEventArgs e) |
{ |
Log.Information($"Client {e.Client?.IpPort} disconnected from network sharing."); |
} |
|
private void Events_ClientConnected(object sender, WatsonTcp.ConnectionEventArgs e) |
{ |
Log.Information($"Client {e.Client?.IpPort} connected to network sharing."); |
} |
|
private void WindowToolStripMenuItem_Click(object sender, EventArgs e) |
{ |
windowToolStripMenuItem.Checked = true; |
@@ -221,6 +336,33 @@ |
} |
} |
|
private void SnapshotDatabase_SnapshotTransferReceived(object sender, SnapshotCreateEventArgs e) |
{ |
switch (e) |
{ |
case SnapshotCreateSuccessEventArgs snapshotCreateSuccessEventArgs: |
if (Configuration.ShowBalloonTooltips) |
{ |
ShowBalloon("Snapshot Transfer Success", $"A snapshot has been transferred {snapshotCreateSuccessEventArgs.Path}.", |
5000); |
} |
|
Log.Information($"A snapshot transfer succeeded {snapshotCreateSuccessEventArgs.Path}."); |
|
break; |
case SnapshotCreateFailureEventArgs snapshotCreateFailureEventArgs: |
if (Configuration.ShowBalloonTooltips) |
{ |
ShowBalloon("Snapshot Transfer Failure", |
$"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}.", 5000); |
} |
|
Log.Information($"A snapshot failed to transfer {snapshotCreateFailureEventArgs.Path}."); |
|
break; |
} |
} |
|
private void SnapshotDatabase_SnapshotRevert(object sender, SnapshotRevertEventArgs e) |
{ |
switch (e) |
@@ -287,6 +429,7 @@ |
showBalloonTooltipsToolStripMenuItem.Checked = Configuration.Enabled; |
windowToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Window; |
screenToolStripMenuItem.Checked = Configuration.CaptureMode == CaptureMode.Screen; |
networkSharingToolStripMenuItem.Checked = Configuration.NetworkSharing; |
|
// Load all tracked folders. |
var folders = await LoadFolders(); |
@@ -408,13 +551,13 @@ |
|
if (_changedFiles.Contains(e.FullPath)) |
{ |
_changedFilesContinuation.Schedule(delay, () => TakeSnapshots(color), _cancellationToken); |
_changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken); |
return; |
} |
|
_changedFiles.Add(e.FullPath); |
|
_changedFilesContinuation.Schedule(delay, () => TakeSnapshots(color), _cancellationToken); |
_changedFilesContinuation.Schedule(delay, async () => await TakeSnapshots(color, _cancellationToken), _cancellationToken); |
} |
catch (Exception exception) |
{ |
@@ -526,7 +669,7 @@ |
color = folder.Color; |
} |
|
await _snapshotDatabase.CreateSnapshot(fileName, file, color, _cancellationToken); |
await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken); |
} |
catch (SQLiteException exception) |
{ |
@@ -556,7 +699,7 @@ |
color = folder.Color; |
} |
|
await _snapshotDatabase.CreateSnapshot(fileName, file, color, _cancellationToken); |
await _snapshotDatabase.CreateSnapshotAsync(fileName, file, color, _cancellationToken); |
} |
catch (SQLiteException exception) |
{ |
@@ -732,51 +875,63 @@ |
} |
} |
|
private async void TakeSnapshots(Color color) |
private async Task TakeSnapshots(Color color, CancellationToken cancellationToken) |
{ |
await _changedFilesLock.WaitAsync(_cancellationToken); |
try |
var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions() {CancellationToken = cancellationToken}); |
var actionBlock = new ActionBlock<string>(async path => |
{ |
foreach (var path in _changedFiles) |
// In case files have vanished strictly due to the time specified by the tracked folders delay. |
if (!File.Exists(path)) |
{ |
// In case files have vanished strictly due to the time specified by the tracked folders delay. |
if (!File.Exists(path)) |
{ |
Log.Warning("File vanished after tracked folder delay.", path); |
Log.Warning("File vanished after tracked folder delay.", path); |
|
continue; |
} |
return; |
} |
|
try |
{ |
var fileName = Path.GetFileName(path); |
var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode); |
try |
{ |
var fileName = System.IO.Path.GetFileName(path); |
var screenCapture = ScreenCapture.Capture((Utilities.CaptureMode)Configuration.CaptureMode); |
|
await _snapshotDatabase.CreateSnapshot(fileName, path, screenCapture, color, |
_cancellationToken); |
} |
catch (SQLiteException exception) |
await _snapshotDatabase.CreateSnapshotAsync(fileName, path, screenCapture, color, |
_cancellationToken); |
} |
catch (SQLiteException exception) |
{ |
if (exception.ResultCode == SQLiteErrorCode.Constraint) |
{ |
if (exception.ResultCode == SQLiteErrorCode.Constraint) |
{ |
Log.Information(exception, "Snapshot already exists."); |
} |
Log.Information(exception, "Snapshot already exists."); |
} |
catch (Exception exception) |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Could not take snapshot.", path); |
} |
}); |
|
using (var snapshotLink = |
bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions() { PropagateCompletion = true })) |
{ |
await _changedFilesLock.WaitAsync(_cancellationToken); |
try |
{ |
foreach (var path in _changedFiles) |
{ |
Log.Error(exception, "Could not take snapshot.", path); |
await bufferBlock.SendAsync(path, cancellationToken); |
} |
bufferBlock.Complete(); |
await bufferBlock.Completion; |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Could not take snapshots."); |
} |
finally |
{ |
_changedFiles.Clear(); |
_changedFilesLock.Release(); |
} |
} |
catch (Exception exception) |
{ |
Log.Error(exception, "Could not take snapshots."); |
} |
finally |
{ |
_changedFiles.Clear(); |
_changedFilesLock.Release(); |
} |
} |
|
#endregion |