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
10 changes: 10 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(dotnet clean:*)",
"Bash(dotnet test:*)",
"Bash(grep:*)"
],
"deny": []
}
}
2 changes: 2 additions & 0 deletions src/SharpSync/Core/SmartConflictResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public async Task<ConflictResolution> ResolveConflictAsync(FileConflictEventArgs
/// Analyzes a conflict to provide rich information for decision making
/// </summary>
private static async Task<ConflictAnalysis> AnalyzeConflictAsync(FileConflictEventArgs conflict, CancellationToken cancellationToken) {
cancellationToken.ThrowIfCancellationRequested();

// Collect analysis data
long localSize = 0;
long remoteSize = 0;
Expand Down
59 changes: 53 additions & 6 deletions src/SharpSync/Sync/SyncEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,56 @@ private async Task<ChangeSet> DetectChangesAsync(SyncOptions? options, Cancellat
}
}

// Handle DeleteExtraneous option - delete files that exist on remote but not locally
if (options?.DeleteExtraneous == true) {
await DetectExtraneousFilesAsync(changeSet, cancellationToken);
}

return changeSet;
}

/// <summary>
/// Detects files that exist on remote but not locally (extraneous files) and marks them for deletion
/// </summary>
private async Task DetectExtraneousFilesAsync(ChangeSet changeSet, CancellationToken cancellationToken) {
// Collect all local paths (both files and directories)
var localPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var addition in changeSet.Additions.Where(a => a.IsLocal)) {
localPaths.Add(addition.Path);
}
foreach (var modification in changeSet.Modifications.Where(m => m.IsLocal)) {
localPaths.Add(modification.Path);
}

// Check all remote items to see if they exist locally
foreach (var addition in changeSet.Additions.Where(a => !a.IsLocal).ToList()) {
if (!localPaths.Contains(addition.Path)) {
// Remote file exists but no corresponding local file - mark for deletion
changeSet.Additions.Remove(addition);
changeSet.Deletions.Add(new DeletionChange {
Path = addition.Path,
DeletedLocally = true,
DeletedRemotely = false,
TrackedState = new SyncState { Path = addition.Path, Status = SyncStatus.RemoteNew }
});
}
}

// Check remote modifications too
foreach (var modification in changeSet.Modifications.Where(m => !m.IsLocal).ToList()) {
if (!localPaths.Contains(modification.Path) && !await _localStorage.ExistsAsync(modification.Path, cancellationToken)) {
// Remote file modified but no local file - mark for deletion
changeSet.Modifications.Remove(modification);
changeSet.Deletions.Add(new DeletionChange {
Path = modification.Path,
DeletedLocally = true,
DeletedRemotely = false,
TrackedState = modification.TrackedState
});
}
}
}

/// <summary>
/// Scans storage for changes compared to tracked state
/// </summary>
Expand Down Expand Up @@ -766,7 +813,7 @@ private async Task ResolveConflictAsync(SyncAction action, ThreadSafeSyncResult
IsDirectory = conflictItem.IsDirectory,
Status = SyncStatus.LocalNew,
LastSyncTime = DateTime.UtcNow,
LocalHash = conflictItem.ETag ?? await _localStorage.ComputeHashAsync(conflictPath, cancellationToken),
LocalHash = conflictItem.IsDirectory ? null : (conflictItem.ETag ?? await _localStorage.ComputeHashAsync(conflictPath, cancellationToken)),
LocalSize = conflictItem.Size,
LocalModified = conflictItem.LastModified
};
Expand Down Expand Up @@ -796,7 +843,7 @@ private async Task ResolveConflictAsync(SyncAction action, ThreadSafeSyncResult
IsDirectory = conflictItem.IsDirectory,
Status = SyncStatus.RemoteNew,
LastSyncTime = DateTime.UtcNow,
RemoteHash = conflictItem.ETag ?? await _remoteStorage.ComputeHashAsync(conflictPath, cancellationToken),
RemoteHash = conflictItem.IsDirectory ? null : (conflictItem.ETag ?? await _remoteStorage.ComputeHashAsync(conflictPath, cancellationToken)),
RemoteSize = conflictItem.Size,
RemoteModified = conflictItem.LastModified
};
Expand Down Expand Up @@ -881,14 +928,14 @@ private async Task UpdateDatabaseStateAsync(ChangeSet changes, CancellationToken
};

if (addition.IsLocal) {
state.LocalHash = addition.Item.ETag ?? await _localStorage.ComputeHashAsync(addition.Path, cancellationToken);
state.LocalHash = addition.Item.IsDirectory ? null : (addition.Item.ETag ?? await _localStorage.ComputeHashAsync(addition.Path, cancellationToken));
state.LocalSize = addition.Item.Size;
state.LocalModified = addition.Item.LastModified;
state.RemoteHash = state.LocalHash;
state.RemoteSize = state.LocalSize;
state.RemoteModified = state.LocalModified;
} else {
state.RemoteHash = addition.Item.ETag ?? await _remoteStorage.ComputeHashAsync(addition.Path, cancellationToken);
state.RemoteHash = addition.Item.IsDirectory ? null : (addition.Item.ETag ?? await _remoteStorage.ComputeHashAsync(addition.Path, cancellationToken));
state.RemoteSize = addition.Item.Size;
state.RemoteModified = addition.Item.LastModified;
state.LocalHash = state.RemoteHash;
Expand All @@ -906,14 +953,14 @@ private async Task UpdateDatabaseStateAsync(ChangeSet changes, CancellationToken
state.LastSyncTime = DateTime.UtcNow;

if (mod.IsLocal) {
state.LocalHash = mod.Item.ETag ?? await _localStorage.ComputeHashAsync(mod.Path, cancellationToken);
state.LocalHash = mod.Item.IsDirectory ? null : (mod.Item.ETag ?? await _localStorage.ComputeHashAsync(mod.Path, cancellationToken));
state.LocalSize = mod.Item.Size;
state.LocalModified = mod.Item.LastModified;
state.RemoteHash = state.LocalHash;
state.RemoteSize = state.LocalSize;
state.RemoteModified = state.LocalModified;
} else {
state.RemoteHash = mod.Item.ETag ?? await _remoteStorage.ComputeHashAsync(mod.Path, cancellationToken);
state.RemoteHash = mod.Item.IsDirectory ? null : (mod.Item.ETag ?? await _remoteStorage.ComputeHashAsync(mod.Path, cancellationToken));
state.RemoteSize = mod.Item.Size;
state.RemoteModified = mod.Item.LastModified;
state.LocalHash = state.RemoteHash;
Expand Down
Loading