QwkSync

Getting Started with QWKSync.NET

This guide provides a minimal first-success path for using QWKSync.NET.

Prerequisites

Installation

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>

Basic Example

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}");
  }
}

Step-by-Step Breakdown

Step 1: Create a Profile

A QwkSyncProfile defines where and how to connect:

QwkSyncProfile profile = new QwkSyncProfile
{
  Endpoint = new Uri("file:///path/to/remote/folder"),
  TransportId = "local-folder"
};

Step 2: Create a Plan

A QwkSyncPlan defines what to do:

QwkSyncPlan plan = new QwkSyncPlan
{
  LocalInboxDirectory = "/path/to/local/inbox",
  LocalOutboxDirectory = "/path/to/local/outbox"
};

The plan uses default settings for discovery, lifecycle, and transfer policy.

Step 3: Create a Client

Create a QwkSyncClient instance:

QwkSyncClient client = new QwkSyncClient();

Step 4: Run the Sync

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);

Step 5: Check the Result

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:

Complete Working Example

Here 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;
    }
  }
}

Next Steps

Customise File Discovery

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
};

Enable Retries

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
};

Add Progress Reporting

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()
};

Handle Cancellation

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.");
}

Common Patterns

Sync with Error Handling

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.");
}

Using a Different Transport

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.

Additional Resources

What QWKSync.NET Does Not Do

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.