diff --git a/src/SharpSync/Core/ConflictAnalysis.cs b/src/SharpSync/Core/ConflictAnalysis.cs index e6e1d02..6c79776 100644 --- a/src/SharpSync/Core/ConflictAnalysis.cs +++ b/src/SharpSync/Core/ConflictAnalysis.cs @@ -95,8 +95,9 @@ public record ConflictAnalysis { public string FormattedSizeDifference => FormatFileSize(SizeDifference); private static string FormatFileSize(long bytes) { - if (bytes == 0) + if (bytes == 0) { return "0 B"; + } string[] suffixes = { "B", "KB", "MB", "GB", "TB" }; int suffixIndex = 0; diff --git a/src/SharpSync/Core/SmartConflictResolver.cs b/src/SharpSync/Core/SmartConflictResolver.cs index d2f700a..f9d0835 100644 --- a/src/SharpSync/Core/SmartConflictResolver.cs +++ b/src/SharpSync/Core/SmartConflictResolver.cs @@ -32,7 +32,7 @@ public async Task ResolveConflictAsync(FileConflictEventArgs var analysis = await AnalyzeConflictAsync(conflict, cancellationToken); // If we have a UI handler, let it decide - if (_conflictHandler != null) { + if (_conflictHandler is not null) { return await _conflictHandler(analysis, cancellationToken); } @@ -56,14 +56,14 @@ private static async Task AnalyzeConflictAsync(FileConflictEve var reasoning = string.Empty; // Analyze file sizes - if (conflict.LocalItem != null && conflict.RemoteItem != null) { + if (conflict.LocalItem is not null && conflict.RemoteItem is not null) { localSize = conflict.LocalItem.Size; remoteSize = conflict.RemoteItem.Size; sizeDifference = Math.Abs(conflict.RemoteItem.Size - conflict.LocalItem.Size); } // Analyze timestamps - if (conflict.LocalItem?.LastModified != null && conflict.RemoteItem?.LastModified != null) { + if (conflict.LocalItem?.LastModified is not null && conflict.RemoteItem?.LastModified is not null) { localModified = conflict.LocalItem.LastModified; remoteModified = conflict.RemoteItem.LastModified; timeDifference = Math.Abs((conflict.RemoteItem.LastModified - conflict.LocalItem.LastModified).TotalSeconds); diff --git a/src/SharpSync/Database/SqliteSyncTransaction.cs b/src/SharpSync/Database/SqliteSyncTransaction.cs index 9420105..92913db 100644 --- a/src/SharpSync/Database/SqliteSyncTransaction.cs +++ b/src/SharpSync/Database/SqliteSyncTransaction.cs @@ -16,8 +16,9 @@ public SqliteSyncTransaction(SQLiteAsyncConnection connection) { } public async Task CommitAsync(CancellationToken cancellationToken = default) { - if (_disposed) + if (_disposed) { throw new ObjectDisposedException(nameof(SqliteSyncTransaction)); + } // SQLite-net handles transactions automatically for batch operations // For explicit transaction control, we could use BeginTransaction/Commit @@ -26,8 +27,9 @@ public async Task CommitAsync(CancellationToken cancellationToken = default) { } public async Task RollbackAsync(CancellationToken cancellationToken = default) { - if (_disposed) + if (_disposed) { throw new ObjectDisposedException(nameof(SqliteSyncTransaction)); + } // SQLite-net handles rollback automatically if transaction is not committed await Task.CompletedTask; diff --git a/src/SharpSync/Storage/LocalFileStorage.cs b/src/SharpSync/Storage/LocalFileStorage.cs index fc7f8d5..3eabd44 100644 --- a/src/SharpSync/Storage/LocalFileStorage.cs +++ b/src/SharpSync/Storage/LocalFileStorage.cs @@ -92,8 +92,9 @@ public async Task> ListItemsAsync(string path, Cancellatio public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) { var fullPath = GetFullPath(path); - if (!File.Exists(fullPath)) + if (!File.Exists(fullPath)) { throw new FileNotFoundException($"File not found: {path}"); + } return await Task.FromResult(File.OpenRead(fullPath)); } @@ -102,8 +103,9 @@ public async Task WriteFileAsync(string path, Stream content, CancellationToken var fullPath = GetFullPath(path); var directory = Path.GetDirectoryName(fullPath); - if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) + if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); + } using var fileStream = File.Create(fullPath); await content.CopyToAsync(fileStream, cancellationToken); @@ -118,10 +120,11 @@ public async Task CreateDirectoryAsync(string path, CancellationToken cancellati public async Task DeleteAsync(string path, CancellationToken cancellationToken = default) { var fullPath = GetFullPath(path); - if (Directory.Exists(fullPath)) + if (Directory.Exists(fullPath)) { Directory.Delete(fullPath, recursive: true); - else if (File.Exists(fullPath)) + } else if (File.Exists(fullPath)) { File.Delete(fullPath); + } await Task.CompletedTask; } @@ -131,15 +134,17 @@ public async Task MoveAsync(string sourcePath, string targetPath, CancellationTo var targetFullPath = GetFullPath(targetPath); var targetDirectory = Path.GetDirectoryName(targetFullPath); - if (!string.IsNullOrEmpty(targetDirectory) && !Directory.Exists(targetDirectory)) + if (!string.IsNullOrEmpty(targetDirectory) && !Directory.Exists(targetDirectory)) { Directory.CreateDirectory(targetDirectory); + } - if (Directory.Exists(sourceFullPath)) + if (Directory.Exists(sourceFullPath)) { Directory.Move(sourceFullPath, targetFullPath); - else if (File.Exists(sourceFullPath)) + } else if (File.Exists(sourceFullPath)) { File.Move(sourceFullPath, targetFullPath, overwrite: true); - else + } else { throw new FileNotFoundException($"Source not found: {sourcePath}"); + } await Task.CompletedTask; } @@ -161,8 +166,9 @@ public async Task GetStorageInfoAsync(CancellationToken cancellatio public async Task ComputeHashAsync(string path, CancellationToken cancellationToken = default) { var fullPath = GetFullPath(path); - if (!File.Exists(fullPath)) + if (!File.Exists(fullPath)) { throw new FileNotFoundException($"File not found: {path}"); + } using var stream = File.OpenRead(fullPath); using var sha256 = SHA256.Create(); @@ -176,8 +182,9 @@ public async Task TestConnectionAsync(CancellationToken cancellationToken } private string GetFullPath(string relativePath) { - if (string.IsNullOrEmpty(relativePath) || relativePath == "/") + if (string.IsNullOrEmpty(relativePath) || relativePath == "/") { return _rootPath; + } // Normalize path separators and remove leading slash relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar); @@ -188,8 +195,9 @@ private string GetFullPath(string relativePath) { var normalizedFullPath = Path.GetFullPath(fullPath); var normalizedRoot = Path.GetFullPath(_rootPath); - if (!normalizedFullPath.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase)) + if (!normalizedFullPath.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase)) { throw new UnauthorizedAccessException($"Path is outside root directory: {relativePath}"); + } return normalizedFullPath; } diff --git a/src/SharpSync/Storage/WebDavStorage.cs b/src/SharpSync/Storage/WebDavStorage.cs index e97e1e5..104307b 100644 --- a/src/SharpSync/Storage/WebDavStorage.cs +++ b/src/SharpSync/Storage/WebDavStorage.cs @@ -113,13 +113,15 @@ public WebDavStorage( /// Gets server capabilities for optimization /// public async Task GetServerCapabilitiesAsync(CancellationToken cancellationToken = default) { - if (_serverCapabilities != null) + if (_serverCapabilities is not null) { return _serverCapabilities; + } await _capabilitiesSemaphore.WaitAsync(cancellationToken); try { - if (_serverCapabilities != null) + if (_serverCapabilities is not null) { return _serverCapabilities; + } _serverCapabilities = await DetectServerCapabilitiesAsync(cancellationToken); return _serverCapabilities; @@ -132,8 +134,9 @@ public async Task GetServerCapabilitiesAsync(CancellationTok /// Authenticates using OAuth2 if configured /// public async Task AuthenticateAsync(CancellationToken cancellationToken = default) { - if (_oauth2Provider == null || _oauth2Config == null) + if (_oauth2Provider is null || _oauth2Config is null) { return true; // No OAuth2 configured, assume basic auth or anonymous + } await _authSemaphore.WaitAsync(cancellationToken); try { @@ -143,7 +146,7 @@ public async Task AuthenticateAsync(CancellationToken cancellationToken = } // Try refresh token first - if (_oauth2Result?.RefreshToken != null) { + if (_oauth2Result?.RefreshToken is not null) { try { _oauth2Result = await _oauth2Provider.RefreshTokenAsync(_oauth2Config, _oauth2Result.RefreshToken, cancellationToken); UpdateClientAuth(); @@ -163,7 +166,7 @@ public async Task AuthenticateAsync(CancellationToken cancellationToken = } private void UpdateClientAuth() { - if (_oauth2Result?.AccessToken != null) { + if (_oauth2Result?.AccessToken is not null) { // Recreate client with OAuth2 token var clientParams = new WebDavClientParams { BaseAddress = new Uri(_baseUrl), @@ -205,16 +208,18 @@ public async Task> ListItemsAsync(string path, Cancellatio }); if (!result.IsSuccessful) { - if (result.StatusCode == 404) + if (result.StatusCode == 404) { return Enumerable.Empty(); + } throw new HttpRequestException($"WebDAV request failed: {result.StatusCode}"); } return result.Resources .Skip(1) // Skip the directory itself + .Where(resource => resource.Uri != null) .Select(resource => new SyncItem { - Path = GetRelativePath(resource.Uri), + Path = GetRelativePath(resource.Uri!), IsDirectory = resource.IsCollection, Size = resource.ContentLength ?? 0, LastModified = resource.LastModifiedDate?.ToUniversalTime() ?? DateTime.MinValue, @@ -236,12 +241,14 @@ public async Task> ListItemsAsync(string path, Cancellatio CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { return null; + } var resource = result.Resources.FirstOrDefault(); - if (resource == null) + if (resource is null) { return null; + } return new SyncItem { Path = path, @@ -270,14 +277,15 @@ public async Task ReadFileAsync(string path, CancellationToken cancellat }); if (!response.IsSuccessful) { - if (response.StatusCode == 404) + if (response.StatusCode == 404) { throw new FileNotFoundException($"File not found: {path}"); + } throw new HttpRequestException($"WebDAV request failed: {response.StatusCode}"); } // For large files, wrap stream with progress reporting - if (needsProgress && item != null) { + if (needsProgress && item is not null) { return new ProgressStream(response.Stream, item.Size, (bytes, total) => RaiseProgressChanged(path, bytes, total, StorageOperation.Download)); } @@ -305,8 +313,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { throw new HttpRequestException($"WebDAV upload failed: {result.StatusCode}"); + } return true; }, cancellationToken); @@ -350,8 +359,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { throw new HttpRequestException($"WebDAV upload failed: {result.StatusCode}"); + } // Report completion RaiseProgressChanged(relativePath, totalSize, totalSize, StorageOperation.Upload); @@ -381,8 +391,9 @@ private async Task WriteFileNextcloudChunkedAsync(string fullPath, string relati while (uploadedBytes < totalSize) { var bytesRead = await content.ReadAsync(buffer, cancellationToken); - if (bytesRead == 0) + if (bytesRead == 0) { break; + } // Upload chunk var chunkPath = $"{chunkFolder}/{chunkNumber:D6}"; @@ -393,8 +404,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { throw new HttpRequestException($"Chunk upload failed: {result.StatusCode}"); + } return true; }, cancellationToken); @@ -445,8 +457,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { throw new HttpRequestException($"Chunk assembly failed: {result.StatusCode}"); + } return true; }, cancellationToken); @@ -464,8 +477,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (existsResult.IsSuccessful) + if (existsResult.IsSuccessful) { return true; // Directory already exists + } var result = await _client.Mkcol(fullPath, new MkColParameters { CancellationToken = cancellationToken @@ -514,8 +528,9 @@ await ExecuteWithRetry(async () => { CancellationToken = cancellationToken }); - if (!result.IsSuccessful) + if (!result.IsSuccessful) { throw new HttpRequestException($"Move failed: {result.StatusCode}"); + } return true; }, cancellationToken); @@ -622,8 +637,9 @@ public async Task ComputeHashAsync(string path, CancellationToken cancel CancellationToken = cancellationToken }); - if (!result.IsSuccessful || result.Resources.Count == 0) + if (!result.IsSuccessful || result.Resources.Count == 0) { return null; + } var resource = result.Resources.First(); @@ -632,11 +648,12 @@ public async Task ComputeHashAsync(string path, CancellationToken cancel p.Name.LocalName == "checksum" && (p.Name.NamespaceName.Contains("owncloud") || p.Name.NamespaceName.Contains("nextcloud"))); - if (checksumProp != null && !string.IsNullOrEmpty(checksumProp.Value)) { + if (checksumProp is not null && !string.IsNullOrEmpty(checksumProp.Value)) { // Format is usually "SHA1:hash" or "MD5:hash" var parts = checksumProp.Value.Split(':'); - if (parts.Length == 2) + if (parts.Length == 2) { return parts[1]; + } } return null; @@ -658,7 +675,7 @@ private async Task DetectServerCapabilitiesAsync(Cancellatio using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) }; // Add auth header if using OAuth2 - if (_oauth2Result?.AccessToken != null) { + if (_oauth2Result?.AccessToken is not null) { httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _oauth2Result.AccessToken); } @@ -739,8 +756,9 @@ private string GetRelativePath(string fullUrl) { } private async Task EnsureAuthenticated(CancellationToken cancellationToken) { - if (_oauth2Provider != null) + if (_oauth2Provider is not null) { return await AuthenticateAsync(cancellationToken); + } return true; } diff --git a/src/SharpSync/Sync/SyncEngine.cs b/src/SharpSync/Sync/SyncEngine.cs index d4d6dda..da02da8 100644 --- a/src/SharpSync/Sync/SyncEngine.cs +++ b/src/SharpSync/Sync/SyncEngine.cs @@ -216,7 +216,8 @@ private async Task ScanStorageAsync( Dictionary trackedItems, bool isLocal, ChangeSet changeSet, - CancellationToken cancellationToken) { + CancellationToken cancellationToken + ) { await ScanDirectoryRecursiveAsync(storage, "", trackedItems, isLocal, changeSet, cancellationToken); } @@ -226,7 +227,8 @@ private async Task ScanDirectoryRecursiveAsync( Dictionary trackedItems, bool isLocal, ChangeSet changeSet, - CancellationToken cancellationToken) { + CancellationToken cancellationToken + ) { try { var items = await storage.ListItemsAsync(dirPath, cancellationToken); var tasks = new List(); @@ -276,7 +278,13 @@ private async Task ScanDirectoryRecursiveAsync( /// /// Determines if an item has changed compared to tracked state /// - private async Task HasChangedAsync(ISyncStorage storage, SyncItem item, SyncState tracked, bool isLocal, CancellationToken cancellationToken) { + private async Task HasChangedAsync( + ISyncStorage storage, + SyncItem item, + SyncState tracked, + bool isLocal, + CancellationToken cancellationToken + ) { if (isLocal) { // Check local changes if (tracked.LocalModified is null) { diff --git a/src/SharpSync/Sync/SyncFilter.cs b/src/SharpSync/Sync/SyncFilter.cs index 01c6583..227e4ca 100644 --- a/src/SharpSync/Sync/SyncFilter.cs +++ b/src/SharpSync/Sync/SyncFilter.cs @@ -16,8 +16,9 @@ public class SyncFilter: ISyncFilter { /// Determines whether a file or directory should be synchronized /// public bool ShouldSync(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (string.IsNullOrWhiteSpace(path)) { return false; + } // Normalize path separators path = path.Replace('\\', '/').Trim('/'); @@ -44,20 +45,23 @@ public bool ShouldSync(string path) { } } - if (!included) + if (!included) { return false; + } } // Check exclude patterns foreach (var pattern in _excludePatterns) { - if (MatchesWildcard(path, pattern)) + if (MatchesWildcard(path, pattern)) { return false; + } } // Check regex excludes foreach (var regex in _excludeRegexes) { - if (regex.IsMatch(path)) + if (regex.IsMatch(path)) { return false; + } } return true; @@ -67,14 +71,16 @@ public bool ShouldSync(string path) { /// Adds an exclusion pattern /// public void AddExclusionPattern(string pattern) { - if (string.IsNullOrWhiteSpace(pattern)) + if (string.IsNullOrWhiteSpace(pattern)) { return; + } // Replace backslashes with forward slashes but preserve trailing slash bool hasTrailingSlash = pattern.EndsWith('/') || pattern.EndsWith('\\'); pattern = pattern.Replace('\\', '/').Trim('/'); - if (hasTrailingSlash && !pattern.EndsWith('/')) + if (hasTrailingSlash && !pattern.EndsWith('/')) { pattern += '/'; + } // If it looks like a regex (contains regex special chars), compile it if (IsRegexPattern(pattern)) { @@ -94,14 +100,16 @@ public void AddExclusionPattern(string pattern) { /// Adds an inclusion pattern /// public void AddInclusionPattern(string pattern) { - if (string.IsNullOrWhiteSpace(pattern)) + if (string.IsNullOrWhiteSpace(pattern)) { return; + } // Replace backslashes with forward slashes but preserve trailing slash bool hasTrailingSlash = pattern.EndsWith('/') || pattern.EndsWith('\\'); pattern = pattern.Replace('\\', '/').Trim('/'); - if (hasTrailingSlash && !pattern.EndsWith('/')) + if (hasTrailingSlash && !pattern.EndsWith('/')) { pattern += '/'; + } // If it looks like a regex, compile it if (IsRegexPattern(pattern)) { @@ -184,10 +192,12 @@ private static bool MatchesWildcard(string path, string pattern) { // Handle simple directory patterns without wildcard (like "temp", ".git", "node_modules") if (!pattern.Contains('*') && !pattern.Contains('?')) { // Check exact match or if path is under this directory - if (path.Equals(pattern, StringComparison.OrdinalIgnoreCase)) + if (path.Equals(pattern, StringComparison.OrdinalIgnoreCase)) { return true; - if (path.StartsWith(pattern + "/", StringComparison.OrdinalIgnoreCase)) + } + if (path.StartsWith(pattern + "/", StringComparison.OrdinalIgnoreCase)) { return true; + } return false; } diff --git a/tests/SharpSync.Tests/Sync/SyncEngineTests.cs b/tests/SharpSync.Tests/Sync/SyncEngineTests.cs index c9e4bd1..8539423 100644 --- a/tests/SharpSync.Tests/Sync/SyncEngineTests.cs +++ b/tests/SharpSync.Tests/Sync/SyncEngineTests.cs @@ -80,7 +80,7 @@ public async Task SynchronizeAsync_EmptyDirectories_ReturnsSuccess() { // Assert Assert.NotNull(result); - if (!result.Success && result.Error != null) { + if (!result.Success && result.Error is not null) { throw new Exception($"Sync failed: {result.Error.Message}", result.Error); } Assert.True(result.Success);