A high-performance .NET wrapper around CSync, providing bi-directional file synchronization capabilities with conflict resolution and progress reporting.
- High-level C# API - Clean, async-friendly interface that wraps all CSync functionality
- Comprehensive error handling - Typed exceptions with detailed error information
- Progress reporting - Real-time progress updates during synchronization
- Conflict resolution - Multiple strategies for handling file conflicts
- Asynchronous operations - Full async/await support with cancellation tokens
- NuGet ready - Ready-to-publish NuGet package with proper metadata
- Cross-platform - Works on Windows, Linux, and macOS
- Extensive documentation - Complete XML documentation for IntelliSense
dotnet add package SharpSyncgit clone https://github.com/Oire/sharp-sync.git
cd sharp-sync
dotnet buildSharpSync requires the CSync native library to be available. There are two options:
Install CSync using your system's package manager:
- Ubuntu/Debian:
sudo apt-get install csync - CentOS/RHEL:
sudo yum install csync - macOS:
brew install csync - Windows: Download from csync.org
The NuGet package can include native CSync libraries. To prepare bundled libraries:
# Windows
cd scripts
.\prepare-native-libs.ps1
# Linux/macOS
cd scripts
chmod +x prepare-native-libs.sh
./prepare-native-libs.shThen place the appropriate CSync binaries in the runtimes directory structure.
using Oire.SharpSync;
// Create a sync engine
using var syncEngine = new SyncEngine();
// Configure sync options
var options = new SyncOptions
{
PreserveTimestamps = true,
DeleteExtraneous = false,
ConflictResolution = ConflictResolution.Ask
};
// Synchronize directories
var result = await syncEngine.SynchronizeAsync(
sourcePath: "/path/to/source",
targetPath: "/path/to/target",
options: options
);
if (result.Success)
{
Console.WriteLine($"Synchronized {result.FilesSynchronized} files");
}
else
{
Console.WriteLine($"Sync failed: {result.Error?.Message}");
}using var syncEngine = new SyncEngine();
// Subscribe to progress events
syncEngine.ProgressChanged += (sender, progress) =>
{
Console.WriteLine($"Progress: {progress.Percentage:F1}% - {progress.CurrentFileName}");
};
var result = await syncEngine.SynchronizeAsync("/source", "/target");using var syncEngine = new SyncEngine();
// Handle conflicts manually
syncEngine.ConflictDetected += (sender, conflict) =>
{
Console.WriteLine($"Conflict: {conflict.SourcePath} vs {conflict.TargetPath}");
// Resolve conflict (Ask user, use source, use target, skip, or merge)
conflict.Resolution = ConflictResolution.UseSource;
};
var result = await syncEngine.SynchronizeAsync("/source", "/target");The main class for performing file synchronization operations.
SynchronizeAsync(string sourcePath, string targetPath, SyncOptions? options = null, CancellationToken cancellationToken = default)- Asynchronously synchronizes files between directoriesSynchronize(string sourcePath, string targetPath, SyncOptions? options = null)- Synchronously synchronizes files between directoriesDispose()- Releases all resources
ProgressChanged- Raised to report synchronization progressConflictDetected- Raised when a file conflict is detected
IsSynchronizing- Gets whether the engine is currently synchronizingLibraryVersion- Gets the CSync library version (static)
Configuration options for synchronization operations.
var options = new SyncOptions
{
PreservePermissions = true, // Preserve file permissions
PreserveTimestamps = true, // Preserve file timestamps
FollowSymlinks = false, // Follow symbolic links
DryRun = false, // Perform a dry run (no changes)
Verbose = false, // Enable verbose logging
ChecksumOnly = false, // Use checksum-only comparison
SizeOnly = false, // Use size-only comparison
DeleteExtraneous = false, // Delete files not in source
UpdateExisting = true, // Update existing files
ConflictResolution = ConflictResolution.Ask, // Conflict resolution strategy
TimeoutSeconds = 0, // Sync timeout (0 = no timeout)
ExcludePatterns = new List<string> // File patterns to exclude
{
"*.tmp",
"*.log",
".DS_Store"
}
};Strategies for resolving file conflicts:
Ask- Ask for user input when conflicts occur (default)UseSource- Always use the source fileUseTarget- Always use the target fileSkip- Skip conflicted filesMerge- Attempt to merge files when possible
Contains the results of a synchronization operation:
public class SyncResult
{
public bool Success { get; } // Whether sync was successful
public long FilesSynchronized { get; } // Number of files synchronized
public long FilesSkipped { get; } // Number of files skipped
public long FilesConflicted { get; } // Number of files with conflicts
public long FilesDeleted { get; } // Number of files deleted
public TimeSpan ElapsedTime { get; } // Total elapsed time
public Exception? Error { get; } // Any error that occurred
public string Details { get; } // Additional details
public long TotalFilesProcessed { get; } // Total files processed
}Progress information during synchronization:
public class SyncProgress
{
public long CurrentFile { get; } // Current file number
public long TotalFiles { get; } // Total number of files
public string CurrentFileName { get; } // Current filename being processed
public double Percentage { get; } // Progress percentage (0-100)
public bool IsCancelled { get; } // Whether operation was cancelled
}SharpSync provides typed exceptions for different error conditions:
try
{
var result = await syncEngine.SynchronizeAsync("/source", "/target");
}
catch (InvalidPathException ex)
{
Console.WriteLine($"Invalid path: {ex.Path} - {ex.Message}");
}
catch (PermissionDeniedException ex)
{
Console.WriteLine($"Permission denied: {ex.Path} - {ex.Message}");
}
catch (FileConflictException ex)
{
Console.WriteLine($"File conflict: {ex.SourcePath} vs {ex.TargetPath}");
}
catch (SyncException ex)
{
Console.WriteLine($"Sync error ({ex.ErrorCode}): {ex.Message}");
}SyncException- Base exception for all sync operationsInvalidPathException- Invalid file or directory pathPermissionDeniedException- Access denied to file or directoryFileConflictException- File conflict detected during syncFileNotFoundException- Required file not found
var options = new SyncOptions
{
TimeoutSeconds = 300 // 5 minute timeout
};
try
{
var result = await syncEngine.SynchronizeAsync("/source", "/target", options);
}
catch (TimeoutException ex)
{
Console.WriteLine($"Synchronization timed out: {ex.Message}");
}var options = new SyncOptions
{
ExcludePatterns = new List<string>
{
"*.tmp", // Exclude temporary files
"*.log", // Exclude log files
"node_modules", // Exclude node_modules directories
".git", // Exclude git repositories
"~*" // Exclude backup files
}
};
var result = await syncEngine.SynchronizeAsync("/source", "/target", options);using var cts = new CancellationTokenSource();
// Cancel after 30 seconds
cts.CancelAfter(TimeSpan.FromSeconds(30));
try
{
var result = await syncEngine.SynchronizeAsync(
"/source",
"/target",
cancellationToken: cts.Token
);
}
catch (OperationCanceledException)
{
Console.WriteLine("Synchronization was cancelled");
}var progressReporter = new Progress<SyncProgress>(progress =>
{
var percentage = progress.Percentage;
var fileName = Path.GetFileName(progress.CurrentFileName);
Console.WriteLine($"[{percentage:F1}%] {fileName}");
// Update UI, save to file, etc.
});
syncEngine.ProgressChanged += (s, p) => progressReporter.Report(p);var syncPairs = new[]
{
("/source1", "/target1"),
("/source2", "/target2"),
("/source3", "/target3")
};
var results = new List<SyncResult>();
foreach (var (source, target) in syncPairs)
{
var result = await syncEngine.SynchronizeAsync(source, target);
results.Add(result);
if (!result.Success)
{
Console.WriteLine($"Failed to sync {source} -> {target}: {result.Error?.Message}");
}
}
var totalSynced = results.Sum(r => r.FilesSynchronized);
Console.WriteLine($"Total files synchronized: {totalSynced}");- .NET 8.0 or later
- CSync native library (see Native Library Requirements above)
SharpSync supports the following platforms:
- Windows: x86, x64
- Linux: x64, ARM64
- macOS: x64, ARM64 (Apple Silicon)
The library automatically detects the platform and loads the appropriate native library.
# Build the solution
dotnet build
# Run tests
dotnet test
# Create NuGet package
dotnet pack --configuration Release- Use
SizeOnly = truefor faster comparisons when file content rarely changes - Set
ChecksumOnly = truefor more accurate comparisons when timestamps are unreliable - Use
DryRun = trueto preview changes before actual synchronization - Enable
Verbose = trueonly for debugging as it impacts performance
SyncEngine instances are not thread-safe. Each thread should use its own SyncEngine instance. However, you can safely run multiple synchronization operations in parallel using different SyncEngine instances.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- CSync - The underlying C library that powers this wrapper
- .NET Community - For the excellent P/Invoke and async patterns