Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/SharpSync/Core/ConflictAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/SharpSync/Core/SmartConflictResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public async Task<ConflictResolution> 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);
}

Expand All @@ -56,14 +56,14 @@ private static async Task<ConflictAnalysis> 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);
Expand Down
6 changes: 4 additions & 2 deletions src/SharpSync/Database/SqliteSyncTransaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down
30 changes: 19 additions & 11 deletions src/SharpSync/Storage/LocalFileStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ public async Task<IEnumerable<SyncItem>> ListItemsAsync(string path, Cancellatio
public async Task<Stream> 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));
}
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -161,8 +166,9 @@ public async Task<StorageInfo> GetStorageInfoAsync(CancellationToken cancellatio
public async Task<string> 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();
Expand All @@ -176,8 +182,9 @@ public async Task<bool> 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);
Expand All @@ -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;
}
Expand Down
64 changes: 41 additions & 23 deletions src/SharpSync/Storage/WebDavStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,15 @@ public WebDavStorage(
/// Gets server capabilities for optimization
/// </summary>
public async Task<ServerCapabilities> 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;
Expand All @@ -132,8 +134,9 @@ public async Task<ServerCapabilities> GetServerCapabilitiesAsync(CancellationTok
/// Authenticates using OAuth2 if configured
/// </summary>
public async Task<bool> 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 {
Expand All @@ -143,7 +146,7 @@ public async Task<bool> 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();
Expand All @@ -163,7 +166,7 @@ public async Task<bool> 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),
Expand Down Expand Up @@ -205,16 +208,18 @@ public async Task<IEnumerable<SyncItem>> ListItemsAsync(string path, Cancellatio
});

if (!result.IsSuccessful) {
if (result.StatusCode == 404)
if (result.StatusCode == 404) {
return Enumerable.Empty<SyncItem>();
}

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,
Expand All @@ -236,12 +241,14 @@ public async Task<IEnumerable<SyncItem>> 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,
Expand Down Expand Up @@ -270,14 +277,15 @@ public async Task<Stream> 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));
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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}";
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -622,8 +637,9 @@ public async Task<string> 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();

Expand All @@ -632,11 +648,12 @@ public async Task<string> 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;
Expand All @@ -658,7 +675,7 @@ private async Task<ServerCapabilities> 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);
}
Expand Down Expand Up @@ -739,8 +756,9 @@ private string GetRelativePath(string fullUrl) {
}

private async Task<bool> EnsureAuthenticated(CancellationToken cancellationToken) {
if (_oauth2Provider != null)
if (_oauth2Provider is not null) {
return await AuthenticateAsync(cancellationToken);
}
return true;
}

Expand Down
Loading
Loading