Horizon

Subversion Repositories:
Compare Path: Rev
With Path: Rev
?path1? @ 1  →  ?path2? @ HEAD
/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