This guide provides a minimal first-success path for using QWKSync.NET.
Add the QWKSync.NET package to your project:
dotnet add package QwkSync
Or add it directly to your .csproj:
<ItemGroup>
<PackageReference Include="QwkSync" Version="1.0.0" />
</ItemGroup>
Here is the simplest possible example using LocalFolderTransport:
using System;
using System.Threading;
using System.Threading.Tasks;
using QwkSync;
class Program
{
static async Task Main(string[] args)
{
// Create a profile pointing to a local folder
QwkSyncProfile profile = new QwkSyncProfile
{
Endpoint = new Uri("file:///path/to/remote/folder"),
TransportId = "local-folder"
};
// Create a plan specifying local directories
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/local/inbox",
LocalOutboxDirectory = "/path/to/local/outbox"
};
// Create a client and run the sync
QwkSyncClient client = new QwkSyncClient();
using CancellationTokenSource cts = new CancellationTokenSource();
QwkSyncResult result = await client.SyncAsync(profile, plan, cts.Token);
// Check the result
Console.WriteLine($"Outcome: {result.Outcome}");
Console.WriteLine($"Issues: {result.Issues.Count}");
}
}
A QwkSyncProfile defines where and how to connect:
QwkSyncProfile profile = new QwkSyncProfile
{
Endpoint = new Uri("file:///path/to/remote/folder"),
TransportId = "local-folder"
};
Endpoint — the URI of the remote endpoint (file URI for local folders)TransportId — the identifier of the transport to use ("local-folder" for the built-in transport)A QwkSyncPlan defines what to do:
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/local/inbox",
LocalOutboxDirectory = "/path/to/local/outbox"
};
LocalInboxDirectory — where downloaded QWK files will be placedLocalOutboxDirectory — where REP files are read from for uploadThe plan uses default settings for discovery, lifecycle, and transfer policy.
Create a QwkSyncClient instance:
QwkSyncClient client = new QwkSyncClient();
Call SyncAsync with the profile, plan, and an optional cancellation token:
using CancellationTokenSource cts = new CancellationTokenSource();
QwkSyncResult result = await client.SyncAsync(profile, plan, cts.Token);
Inspect the QwkSyncResult:
Console.WriteLine($"Outcome: {result.Outcome}");
Console.WriteLine($"Issues: {result.Issues.Count}");
if (result.Issues.Count > 0)
{
foreach (QwkSyncIssue issue in result.Issues)
{
Console.WriteLine($" - {issue.Description}");
}
}
The Outcome can be:
QwkSyncOutcome.Success — all operations completed successfullyQwkSyncOutcome.PartialSuccess — some operations succeeded, some failedQwkSyncOutcome.Failed — all operations failed or sync could not proceedHere is a complete example that you can run:
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using QwkSync;
class Program
{
static async Task<int> Main(string[] args)
{
try
{
// Set up directories
string remoteFolder = Path.Combine(Path.GetTempPath(), "qwkremote");
string localInbox = Path.Combine(Path.GetTempPath(), "qwkinbox");
string localOutbox = Path.Combine(Path.GetTempPath(), "qwkoutbox");
Directory.CreateDirectory(remoteFolder);
Directory.CreateDirectory(localInbox);
Directory.CreateDirectory(localOutbox);
// Create a dummy QWK file in remote folder for demonstration
string testQwkFile = Path.Combine(remoteFolder, "test.qwk");
File.WriteAllText(testQwkFile, "dummy QWK content");
// Create profile
string remoteFolderUri = Path.GetFullPath(remoteFolder);
string uriString = "file://" + remoteFolderUri.Replace("\\", "/");
if (!uriString.StartsWith("file:///"))
{
uriString = "file:///" + uriString.Substring("file://".Length);
}
QwkSyncProfile profile = new QwkSyncProfile
{
Endpoint = new Uri(uriString),
TransportId = "local-folder"
};
// Create plan
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = localInbox,
LocalOutboxDirectory = localOutbox
};
// Create client and sync
QwkSyncClient client = new QwkSyncClient();
using CancellationTokenSource cts = new CancellationTokenSource();
QwkSyncResult result = await client.SyncAsync(profile, plan, cts.Token);
// Display results
Console.WriteLine($"Sync completed with outcome: {result.Outcome}");
Console.WriteLine($"Issues encountered: {result.Issues.Count}");
if (result.Issues.Count > 0)
{
Console.WriteLine("Issues:");
foreach (QwkSyncIssue issue in result.Issues)
{
Console.WriteLine($" - {issue.Description}");
}
}
// Check if file was downloaded
string[] downloadedFiles = Directory.GetFiles(localInbox, "*.qwk");
Console.WriteLine($"Files in local inbox: {downloadedFiles.Length}");
return result.Outcome == QwkSyncOutcome.Failed ? 1 : 0;
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled.");
return 130;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
return 1;
}
}
}
Control which files are discovered and how they are ordered:
PacketDiscovery discovery = new PacketDiscovery
{
QwkSearchPattern = "*.qwk",
RepSearchPattern = "*.rep",
PickStrategy = PacketPickStrategy.Newest,
RemoteInboxPath = "inbox",
RemoteOutboxPath = "outbox"
};
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/inbox",
LocalOutboxDirectory = "/path/to/outbox",
Discovery = discovery
};
Configure retry behaviour:
TransferPolicy policy = new TransferPolicy
{
MaxRetries = 3,
Timeout = TimeSpan.FromMinutes(5)
};
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/inbox",
LocalOutboxDirectory = "/path/to/outbox",
Transfer = policy
};
Monitor sync progress:
class ConsoleProgress : IProgress<QwkSyncProgress>
{
public void Report(QwkSyncProgress value)
{
if (value.TotalBytes.HasValue)
{
long percentage = value.TotalBytes.Value > 0
? (value.BytesTransferred * 100) / value.TotalBytes.Value
: 0;
Console.WriteLine($"Progress: {value.BytesTransferred:N0} / {value.TotalBytes.Value:N0} bytes ({percentage}%)");
}
else
{
Console.WriteLine($"Progress: {value.BytesTransferred:N0} bytes");
}
}
}
QwkSyncPlan plan = new QwkSyncPlan
{
LocalInboxDirectory = "/path/to/inbox",
LocalOutboxDirectory = "/path/to/outbox",
Progress = new ConsoleProgress()
};
Support cancellation gracefully:
using CancellationTokenSource cts = new CancellationTokenSource();
// Set up cancellation (e.g., from user input)
Console.CancelKeyPress += (sender, e) =>
{
e.Cancel = true;
Console.WriteLine("Cancelling...");
cts.Cancel();
};
try
{
QwkSyncResult result = await client.SyncAsync(profile, plan, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Sync was cancelled.");
}
try
{
QwkSyncResult result = await client.SyncAsync(profile, plan, cancellationToken);
switch (result.Outcome)
{
case QwkSyncOutcome.Success:
Console.WriteLine("Sync completed successfully.");
break;
case QwkSyncOutcome.PartialSuccess:
Console.WriteLine("Sync completed with some issues:");
foreach (QwkSyncIssue issue in result.Issues)
{
Console.WriteLine($" - {issue.Description}");
}
break;
case QwkSyncOutcome.Failed:
Console.WriteLine("Sync failed:");
foreach (QwkSyncIssue issue in result.Issues)
{
Console.WriteLine($" - {issue.Description}");
}
break;
}
}
catch (QwkSyncConcurrencyException ex)
{
Console.WriteLine($"Concurrency conflict: {ex.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Sync was cancelled.");
}
If you have a transport extension (e.g., HTTP transport):
QwkSyncProfile profile = new QwkSyncProfile
{
Endpoint = new Uri("https://example.com/api"),
TransportId = "http" // Must match the transport factory's ID
};
Ensure the transport extension package is referenced in your project.
Remember: QWKSync.NET does not parse, validate, or interpret QWK/REP file contents. It only moves files. For packet parsing and validation, use a separate library or your own code.