/trunk/WingMan/AutoCompletion/AutoCompletion.cs |
@@ -0,0 +1,168 @@ |
using System; |
using System.Runtime.InteropServices; |
using System.Threading; |
using System.Threading.Tasks; |
using System.Windows.Forms; |
using Mono.Data.Sqlite; |
|
namespace WingMan.AutoCompletion |
{ |
public class AutoCompletion : IDisposable |
{ |
public delegate void LoadFailed(object sender, AutoCompletionFailedEventArgs args); |
|
|
public delegate void SaveFailed(object sender, AutoCompletionFailedEventArgs args); |
|
public AutoCompletion() |
{ |
} |
|
public AutoCompletion(TaskScheduler taskScheduler, CancellationToken cancellationToken) : this() |
{ |
TaskScheduler = taskScheduler; |
CancellationToken = cancellationToken; |
} |
|
private TaskScheduler TaskScheduler { get; } |
private CancellationToken CancellationToken { get; } |
|
public void Dispose() |
{ |
} |
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
[return: MarshalAs(UnmanagedType.Bool)] |
private static extern bool SetDllDirectory(string lpPathName); |
|
public event SaveFailed OnSaveFailed; |
|
public event LoadFailed OnLoadFailed; |
|
public async Task Save(string name, AutoCompleteStringCollection autoCompleteStringCollection) |
{ |
SetDllDirectory(Environment.Is64BitOperatingSystem ? "x64" : "x86"); |
|
try |
{ |
using (var sqliteConnection = |
new SqliteConnection($"URI=file:{"Autocomplete.db"}")) |
{ |
await sqliteConnection.OpenAsync(CancellationToken); |
|
// Create table if it does not exist. |
using (var sqliteCommand = |
new SqliteCommand($"CREATE TABLE IF NOT EXISTS {name} (data text UNIQUE NOT NULL)", |
sqliteConnection)) |
{ |
using (var dbtransaction = sqliteConnection.BeginTransaction()) |
{ |
try |
{ |
await sqliteCommand.ExecuteReaderAsync(CancellationToken); |
|
dbtransaction.Commit(); |
} |
catch |
{ |
dbtransaction.Rollback(); |
throw; |
} |
} |
} |
|
// Add all items to the database. |
await Task.Delay(0, CancellationToken).ContinueWith(async _ => |
{ |
foreach (var sourceItem in autoCompleteStringCollection) |
{ |
var data = sourceItem.ToString(); |
|
using (var sqliteCommand = |
new SqliteCommand($"REPLACE INTO {name} (data) VALUES (:data)", |
sqliteConnection)) |
{ |
sqliteCommand |
.Parameters |
.Add(new SqliteParameter("data", data)); |
|
using (var dbtransaction = sqliteConnection.BeginTransaction()) |
{ |
try |
{ |
await sqliteCommand.ExecuteReaderAsync(CancellationToken); |
|
dbtransaction.Commit(); |
} |
catch |
{ |
dbtransaction.Rollback(); |
throw; |
} |
} |
} |
} |
}, CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
} |
} |
catch (Exception ex) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnSaveFailed?.Invoke(this, |
new AutoCompletionFailedEventArgs(AutoCompletionFailedType.Save, name, ex)), CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
} |
} |
|
public async Task Load(string name, AutoCompleteStringCollection autoCompleteStringCollection) |
{ |
SetDllDirectory(Environment.Is64BitOperatingSystem ? "x64" : "x86"); |
|
try |
{ |
using (var sqliteConnection = |
new SqliteConnection($"URI=file:{"Autocomplete.db"}")) |
{ |
await sqliteConnection.OpenAsync(CancellationToken); |
using (var sqliteCommand = |
new SqliteCommand($"SELECT data FROM {name}", sqliteConnection)) |
{ |
using (var dbtransaction = sqliteConnection.BeginTransaction()) |
{ |
try |
{ |
using (var reader = await sqliteCommand.ExecuteReaderAsync(CancellationToken)) |
{ |
while (await reader.ReadAsync(CancellationToken)) |
for (var i = 0; i < reader.FieldCount; ++i) |
{ |
var value = reader.GetString(i); |
if (string.IsNullOrEmpty(value)) |
continue; |
|
await Task.Delay(0, CancellationToken).ContinueWith( |
_ => autoCompleteStringCollection.Add(value), CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
} |
} |
|
dbtransaction.Commit(); |
} |
catch |
{ |
dbtransaction.Rollback(); |
throw; |
} |
} |
} |
} |
} |
catch (Exception ex) |
{ |
await Task.Delay(0, CancellationToken).ContinueWith(_ => OnLoadFailed?.Invoke(this, |
new AutoCompletionFailedEventArgs(AutoCompletionFailedType.Load, name, ex)), CancellationToken, |
TaskContinuationOptions.None, TaskScheduler); |
} |
} |
} |
} |
/trunk/WingMan/WingMan.csproj |
@@ -1,5 +1,6 @@ |
<?xml version="1.0" encoding="utf-8"?> |
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Import Project="..\packages\SQLite.Native.3.12.3\build\net45\SQLite.Native.props" Condition="Exists('..\packages\SQLite.Native.3.12.3\build\net45\SQLite.Native.props')" /> |
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> |
<PropertyGroup> |
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
@@ -23,6 +24,7 @@ |
<DefineConstants>DEBUG;TRACE</DefineConstants> |
<ErrorReport>prompt</ErrorReport> |
<WarningLevel>4</WarningLevel> |
<Prefer32Bit>false</Prefer32Bit> |
</PropertyGroup> |
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> |
<PlatformTarget>AnyCPU</PlatformTarget> |
@@ -40,6 +42,9 @@ |
<Reference Include="Gma.System.MouseKeyHook, Version=5.6.130.0, Culture=neutral, processorArchitecture=MSIL"> |
<HintPath>..\packages\MouseKeyHook.5.6.0\lib\net40\Gma.System.MouseKeyHook.dll</HintPath> |
</Reference> |
<Reference Include="Mono.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> |
<HintPath>..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\lib\net4\Mono.Data.Sqlite.dll</HintPath> |
</Reference> |
<Reference Include="MQTTnet, Version=2.8.4.0, Culture=neutral, PublicKeyToken=b69712f52770c0a7, processorArchitecture=MSIL"> |
<HintPath>..\packages\MQTTnet.2.8.4\lib\net452\MQTTnet.dll</HintPath> |
</Reference> |
@@ -51,9 +56,16 @@ |
</Reference> |
<Reference Include="System" /> |
<Reference Include="System.Core" /> |
<Reference Include="System.Data.Portable, Version=4.0.0.0, Culture=neutral, PublicKeyToken=59e704a76bc4613a, processorArchitecture=MSIL"> |
<HintPath>..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\lib\net4\System.Data.Portable.dll</HintPath> |
</Reference> |
<Reference Include="System.Threading.Tasks.Dataflow, Version=4.5.24.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> |
<HintPath>..\packages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll</HintPath> |
</Reference> |
<Reference Include="System.Transactions" /> |
<Reference Include="System.Transactions.Portable, Version=4.0.0.0, Culture=neutral, PublicKeyToken=59e704a76bc4613a, processorArchitecture=MSIL"> |
<HintPath>..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\lib\net4\System.Transactions.Portable.dll</HintPath> |
</Reference> |
<Reference Include="System.Xml.Linq" /> |
<Reference Include="System.Data.DataSetExtensions" /> |
<Reference Include="Microsoft.CSharp" /> |
@@ -68,6 +80,9 @@ |
</Reference> |
</ItemGroup> |
<ItemGroup> |
<Compile Include="AutoCompletion\AutoCompletion.cs" /> |
<Compile Include="AutoCompletion\AutoCompletionFailedEventArgs.cs" /> |
<Compile Include="AutoCompletion\AutoCompletionFailedType.cs" /> |
<Compile Include="Communication\MqttAuthenticationFailureEventArgs.cs" /> |
<Compile Include="Lobby\LobbyMessageReceivedEventArgs.cs" /> |
<Compile Include="Lobby\LobbyMessageSynchronizer.cs" /> |
@@ -140,4 +155,15 @@ |
<Content Include="wingman.ico" /> |
</ItemGroup> |
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> |
<Import Project="..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\tools\Mono.Data.Sqlite.Portable.targets" Condition="Exists('..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\tools\Mono.Data.Sqlite.Portable.targets')" /> |
<Target Name="EnsureMonoDataSqlitePortableImported" BeforeTargets="BeforeBuild" Condition="'$(MonoDataSqlitePortableImported)' == ''"> |
<Error Condition="!Exists('..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\tools\Mono.Data.Sqlite.Portable.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them." /> |
<Error Condition="Exists('..\packages\Mono.Data.Sqlite.Portable.1.0.3.5\tools\Mono.Data.Sqlite.Portable.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build." /> |
</Target> |
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> |
<PropertyGroup> |
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> |
</PropertyGroup> |
<Error Condition="!Exists('..\packages\SQLite.Native.3.12.3\build\net45\SQLite.Native.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\SQLite.Native.3.12.3\build\net45\SQLite.Native.props'))" /> |
</Target> |
</Project> |
/trunk/WingMan/WingManForm.Designer.cs |
@@ -273,7 +273,7 @@ |
// label6 |
// |
this.label6.AutoSize = true; |
this.label6.Location = new System.Drawing.Point(279, 20); |
this.label6.Location = new System.Drawing.Point(287, 20); |
this.label6.Name = "label6"; |
this.label6.Size = new System.Drawing.Size(53, 13); |
this.label6.TabIndex = 10; |
@@ -282,10 +282,10 @@ |
// Password |
// |
this.Password.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; |
this.Password.Location = new System.Drawing.Point(336, 16); |
this.Password.Location = new System.Drawing.Point(344, 16); |
this.Password.Name = "Password"; |
this.Password.PasswordChar = '*'; |
this.Password.Size = new System.Drawing.Size(104, 20); |
this.Password.Size = new System.Drawing.Size(152, 20); |
this.Password.TabIndex = 9; |
// |
// label5 |
@@ -299,6 +299,8 @@ |
// |
// Nick |
// |
this.Nick.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest; |
this.Nick.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; |
this.Nick.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; |
this.Nick.Location = new System.Drawing.Point(152, 48); |
this.Nick.Name = "Nick"; |
@@ -319,7 +321,7 @@ |
// label4 |
// |
this.label4.AutoSize = true; |
this.label4.Location = new System.Drawing.Point(185, 20); |
this.label4.Location = new System.Drawing.Point(194, 20); |
this.label4.Name = "label4"; |
this.label4.Size = new System.Drawing.Size(26, 13); |
this.label4.TabIndex = 3; |
@@ -338,8 +340,10 @@ |
// |
// Port |
// |
this.Port.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest; |
this.Port.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; |
this.Port.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; |
this.Port.Location = new System.Drawing.Point(215, 16); |
this.Port.Location = new System.Drawing.Point(224, 16); |
this.Port.Name = "Port"; |
this.Port.Size = new System.Drawing.Size(60, 20); |
this.Port.TabIndex = 2; |
@@ -349,7 +353,7 @@ |
// label3 |
// |
this.label3.AutoSize = true; |
this.label3.Location = new System.Drawing.Point(40, 20); |
this.label3.Location = new System.Drawing.Point(8, 20); |
this.label3.Name = "label3"; |
this.label3.Size = new System.Drawing.Size(29, 13); |
this.label3.TabIndex = 1; |
@@ -357,10 +361,12 @@ |
// |
// Address |
// |
this.Address.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Suggest; |
this.Address.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; |
this.Address.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; |
this.Address.Location = new System.Drawing.Point(76, 16); |
this.Address.Location = new System.Drawing.Point(40, 16); |
this.Address.Name = "Address"; |
this.Address.Size = new System.Drawing.Size(104, 20); |
this.Address.Size = new System.Drawing.Size(152, 20); |
this.Address.TabIndex = 0; |
this.Address.Text = "0.0.0.0"; |
this.Address.Click += new System.EventHandler(this.AddressTextBoxClick); |
/trunk/WingMan/WingManForm.cs |
@@ -11,6 +11,7 @@ |
using Gma.System.MouseKeyHook; |
using MQTTnet.Extensions.ManagedClient; |
using MQTTnet.Server; |
using WingMan.AutoCompletion; |
using WingMan.Bindings; |
using WingMan.Communication; |
using WingMan.Lobby; |
@@ -28,6 +29,15 @@ |
FormTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); |
FormCancellationTokenSource = new CancellationTokenSource(); |
|
// Set up autocompletion. |
AutoCompletion = new AutoCompletion.AutoCompletion(FormTaskScheduler, FormCancellationTokenSource.Token); |
AutoCompletion.OnSaveFailed += AutoCompletionOnSaveFailed; |
AutoCompletion.OnLoadFailed += AutoCompletionOnLoadFailed; |
|
Task.Run(() => AutoCompletion.Load(Address.Name, Address.AutoCompleteCustomSource)); |
Task.Run(() => AutoCompletion.Load(Port.Name, Address.AutoCompleteCustomSource)); |
Task.Run(() => AutoCompletion.Load(Nick.Name, Nick.AutoCompleteCustomSource)); |
|
MqttCommunication = new MqttCommunication(FormTaskScheduler, FormCancellationTokenSource.Token); |
MqttCommunication.OnClientAuthenticationFailed += OnMqttClientAuthenticationFailed; |
MqttCommunication.OnClientConnectionFailed += OnMqttClientConnectionFailed; |
@@ -84,6 +94,7 @@ |
KeySimulator.OnMouseKeyBindingExecuting += OnMouseKeyBindingExecuting; |
} |
|
private static AutoCompletion.AutoCompletion AutoCompletion { get; set; } |
private static CancellationTokenSource FormCancellationTokenSource { get; set; } |
|
private static TaskScheduler FormTaskScheduler { get; set; } |
@@ -112,6 +123,18 @@ |
|
public KeySimulator KeySimulator { get; set; } |
|
private void AutoCompletionOnLoadFailed(object sender, AutoCompletionFailedEventArgs args) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_loading_autocomplete_source} : {args.Name} : {args.Exception.Message}{Environment.NewLine}"); |
} |
|
private void AutoCompletionOnSaveFailed(object sender, AutoCompletionFailedEventArgs args) |
{ |
ActivityTextBox.AppendText( |
$"{Strings.Failed_saving_autocomplete_source} : {args.Name} : {args.Exception.Message}{Environment.NewLine}"); |
} |
|
/// <inheritdoc /> |
/// <summary> |
/// Clean up any resources being used. |
@@ -292,6 +315,8 @@ |
if (!ValidateConnectionParameters(out var ipAddress, out var port, out var nick, out var password)) |
return; |
|
StoreConnectionAutocomplete(); |
|
// Start the MQTT server. |
if (!await MqttCommunication |
.Start(MqttCommunicationType.Server, ipAddress, port, nick, password)) |
@@ -311,6 +336,21 @@ |
Password.Enabled = false; |
} |
|
private async void StoreConnectionAutocomplete() |
{ |
Address.AutoCompleteCustomSource.Add(Address.Text); |
|
await AutoCompletion.Save(Address.Name, Address.AutoCompleteCustomSource); |
|
Port.AutoCompleteCustomSource.Add(Port.Text); |
|
await AutoCompletion.Save(Port.Name, Port.AutoCompleteCustomSource); |
|
Nick.AutoCompleteCustomSource.Add(Nick.Text); |
|
await AutoCompletion.Save(Nick.Name, Nick.AutoCompleteCustomSource); |
} |
|
private bool ValidateConnectionParameters( |
out IPAddress address, |
out int port, |
@@ -399,6 +439,8 @@ |
if (!ValidateConnectionParameters(out var ipAddress, out var port, out var nick, out var password)) |
return; |
|
StoreConnectionAutocomplete(); |
|
if (!await MqttCommunication |
.Start(MqttCommunicationType.Client, ipAddress, port, nick, password)) |
{ |
@@ -620,6 +662,22 @@ |
} |
} |
|
private void WingManFormResized(object sender, EventArgs e) |
{ |
if (WindowState == FormWindowState.Minimized) |
{ |
Hide(); |
notifyIcon1.Visible = true; |
} |
} |
|
private void NotifyIconDoubleClick(object sender, EventArgs e) |
{ |
Show(); |
WindowState = FormWindowState.Normal; |
notifyIcon1.Visible = false; |
} |
|
#region Saving and loading |
|
private async Task SaveLocalMouseKeyBindings() |
@@ -725,21 +783,5 @@ |
} |
|
#endregion |
|
private void WingManFormResized(object sender, EventArgs e) |
{ |
if (WindowState == FormWindowState.Minimized) |
{ |
Hide(); |
notifyIcon1.Visible = true; |
} |
} |
|
private void NotifyIconDoubleClick(object sender, EventArgs e) |
{ |
Show(); |
WindowState = FormWindowState.Normal; |
notifyIcon1.Visible = false; |
} |
} |
} |