Safe File Copy, Move, and Directory Sync in C#
Robust file operations with retry logic, timestamp-based change detection, and conflict awareness. Patterns from a production PDM file sync tool.
public static class SafeFileOps
{
/// <summary>Copy with exponential backoff retry.</summary>
public static void CopyWithRetry(string src, string dest, int maxRetries = 3)
{
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
File.Copy(src, dest, overwrite: true);
return;
}
catch (IOException) when (attempt < maxRetries)
{
Thread.Sleep((int)Math.Pow(2, attempt) * 200);
}
}
}
/// <summary>Sync only changed files (2-second tolerance).</summary>
public static int SyncDirectory(string sourceDir, string destDir)
{
int synced = 0;
Directory.CreateDirectory(destDir);
foreach (var srcFile in Directory.GetFiles(sourceDir, "*",
SearchOption.AllDirectories))
{
var relativePath = Path.GetRelativePath(sourceDir, srcFile);
var destFile = Path.Combine(destDir, relativePath);
if (File.Exists(destFile))
{
var srcTime = File.GetLastWriteTime(srcFile);
var destTime = File.GetLastWriteTime(destFile);
// 2-second tolerance for filesystem precision
if (Math.Abs((srcTime - destTime).TotalSeconds) < 2)
continue;
}
Directory.CreateDirectory(Path.GetDirectoryName(destFile)!);
CopyWithRetry(srcFile, destFile);
synced++;
}
return synced;
}
}How It Works
The CopyWithRetry method handles the most common file operation failure: another process holding a lock. Instead of failing immediately, it retries with exponential backoff (200ms, 400ms, 800ms). The when filter ensures only IOException triggers retry — other exceptions (permission denied, path not found) fail immediately.
The SyncDirectory method walks the source tree and only copies files that have actually changed, using a timestamp comparison with tolerance.
Why 2-Second Tolerance
File system timestamp precision varies dramatically:
| File System | Precision |
|---|---|
| NTFS | 100 nanoseconds |
| FAT32 | 2 seconds |
| exFAT | 10 milliseconds |
| Network shares | Variable (often rounded to seconds) |
When syncing between an NTFS local drive and a FAT32 USB or network share, a file copied at 10:30:00.500 might be stored as 10:30:00 on the destination. Without tolerance, the next sync would see a difference and re-copy everything. The 2-second window handles all common cases.
Integration Tips
- Combine with FileSystemWatcher: Use
FileChangeMonitorfor real-time detection, thenSyncDirectoryfor the actual copy. The watcher tells you *when* to sync; the sync method handles *what* to copy. - Avoid locks: Open source files with
FileShare.Readto prevent blocking other processes:
using var stream = new FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.Read);- Long paths: If your paths might exceed 260 characters (common with nested SolidWorks assemblies), prefix with
\\?\or use .NET 8+ which handles long paths natively. - Read-only files: Check
File.GetAttributesbefore overwriting — some files (library parts, toolbox components) are intentionally read-only.