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
33 changes: 33 additions & 0 deletions .claude/agent-memory/dotnet-style-corrector/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,36 @@ Test code in this project follows consistent patterns:
- Descriptive test names with `MethodName_Scenario_ExpectedResult` format
- Good use of collection expressions (`Array.Empty<T>()`, `new[] { ... }`)
- Thread safety tests use proper error capturing with lock statements

### Sample Code Review (2026-02-13)
Reviewed all files in `samples/SharpSync.Samples.Console/`:
- Fixed brace placement for all class declarations, method declarations, control flow statements, and exception handlers
- Fixed object initializer bracing (dictionaries, object initializers with `new`)
- Files affected: `Program.cs`, `BasicSyncExample.cs`, `ConsoleOAuth2Example.cs`
- All files now comply with `.editorconfig` brace placement rules
- Build verification: ✅ No compilation errors

## Style Enforcement Learnings

### Opening Brace Placement
Per `.editorconfig`: `csharp_new_line_before_open_brace = none` means opening braces go on the SAME line as the preceding code element (class name, method name, if/for/while, catch/finally, etc.). This project uses Allman/BSD style consistently.

**Correct pattern:**
```csharp
public class MyClass
{
public void MyMethod()
{
if (condition)
{
// code
}
}
}
```

### Common Patterns Requiring Fixes in Sample Code
1. **Lambda expressions with blocks**: Opening brace on new line after `=>`
2. **Dictionary initializers**: Opening brace on new line after `new Dictionary<TKey, TValue>`
3. **Exception handlers**: `catch` and `finally` clauses need opening braces on new line
4. **Else clauses**: Despite setting `csharp_new_line_before_else = false`, opening brace for else block goes on new line
15 changes: 9 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,12 @@ See `src/SharpSync/SharpSync.csproj` for current versions.
│ ├── Database/
│ ├── Storage/
│ └── Sync/
├── examples/ # Usage examples
│ ├── BasicSyncExample.cs
│ ├── ConsoleOAuth2Example.cs
├── samples/ # Runnable sample applications
│ ├── SharpSync.Samples.Console/ # Interactive console demo
│ │ ├── SharpSync.Samples.Console.csproj
│ │ ├── Program.cs
│ │ ├── BasicSyncExample.cs
│ │ └── ConsoleOAuth2Example.cs
│ └── README.md
└── .github/
└── workflows/ # CI/CD configuration
Expand Down Expand Up @@ -488,7 +491,7 @@ The core library is production-ready. All critical items are complete and the li
- `.editorconfig` with comprehensive C# style rules
- Multi-platform CI/CD pipeline (Ubuntu, Windows, macOS with matrix strategy)
- Integration tests for all storage backends (SFTP, FTP, S3, WebDAV) via Docker on Ubuntu
- Examples directory with working samples
- Samples directory with buildable sample project

### 🚨 CRITICAL (Must Fix Before v1.0)

Expand All @@ -501,7 +504,7 @@ All critical items have been resolved.
- ✅ All storage implementations tested (LocalFileStorage, SftpStorage, FtpStorage, S3Storage, WebDavStorage)
- ✅ README matches actual API
- ✅ No TODOs/FIXMEs in code
- ✅ Examples directory exists
- ✅ Samples directory with buildable project
- ✅ Package metadata accurate
- ✅ Integration test infrastructure (Docker-based CI for all backends)
- ✅ Multi-platform CI (Ubuntu, Windows, macOS)
Expand All @@ -528,7 +531,7 @@ All critical items have been resolved.
- ✅ Per-file progress events (`FileProgressChanged` on `ISyncEngine`, `FileProgressEventArgs`, `FileTransferOperation`)
- ✅ Examples directory with working samples
- ✅ Code coverage reporting (Coverlet + Codecov with badge in README)
- ✅ Console OAuth2 provider example (`examples/ConsoleOAuth2Example.cs`)
- ✅ Console OAuth2 provider example (`samples/SharpSync.Samples.Console/ConsoleOAuth2Example.cs`)
- ✅ All `SyncOptions` properties wired and functional (TimeoutSeconds, ChecksumOnly, SizeOnly, UpdateExisting, ConflictResolution override, ExcludePatterns, Verbose, FollowSymlinks, PreserveTimestamps, PreservePermissions)
- ✅ `ISyncStorage.SetLastModifiedAsync` / `SetPermissionsAsync` default interface methods
- ✅ Symlink detection (`SyncItem.IsSymlink`) in Local and SFTP storage
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ var result = await engine.SynchronizeAsync();
var resolver = new SmartConflictResolver(
conflictHandler: async (analysis, ct) =>
{
// analysis contains: LocalSize, RemoteSize, LocalModified, RemoteModified,
// DetectedNewer, Recommendation, ReasonForRecommendation
Console.WriteLine($"Conflict: {analysis.Path}");
// analysis contains: FilePath, LocalSize, RemoteSize, LocalModified,
// RemoteModified, NewerVersion, RecommendedResolution
Console.WriteLine($"Conflict: {analysis.FilePath}");
Console.WriteLine($" Local: {analysis.LocalModified}, Remote: {analysis.RemoteModified}");
Console.WriteLine($" Recommendation: {analysis.Recommendation}");
Console.WriteLine($" Recommendation: {analysis.RecommendedResolution}");

// Return user's choice
return analysis.Recommendation;
return analysis.RecommendedResolution;
},
defaultResolution: ConflictResolution.Ask
);
Expand Down Expand Up @@ -239,7 +239,7 @@ var plan = await engine.GetSyncPlanAsync();

Console.WriteLine($"Downloads: {plan.Downloads.Count}");
Console.WriteLine($"Uploads: {plan.Uploads.Count}");
Console.WriteLine($"Deletes: {plan.Deletes.Count}");
Console.WriteLine($"Deletes: {plan.DeleteCount}");
Console.WriteLine($"Conflicts: {plan.Conflicts.Count}");

foreach (var action in plan.Downloads)
Expand Down Expand Up @@ -484,6 +484,17 @@ dotnet test
dotnet pack --configuration Release
```

## Samples

The [`samples/`](samples/) directory contains a buildable console application demonstrating SharpSync features:

```bash
cd samples/SharpSync.Samples.Console
dotnet run
```

See the [samples README](samples/README.md) for details.

## Contributing

1. Fork the repository
Expand Down
39 changes: 38 additions & 1 deletion SharpSync.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,66 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5D20AA90-6969-D8BD-9DCD-8634F4692FDA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpSync.Samples.Console", "samples\SharpSync.Samples.Console\SharpSync.Samples.Console.csproj", "{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|x64.ActiveCfg = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|x64.Build.0 = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|x86.ActiveCfg = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Debug|x86.Build.0 = Debug|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|Any CPU.Build.0 = Release|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|x64.ActiveCfg = Release|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|x64.Build.0 = Release|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|x86.ActiveCfg = Release|Any CPU
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0}.Release|x86.Build.0 = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|x64.Build.0 = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Debug|x86.Build.0 = Debug|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|Any CPU.Build.0 = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|x64.ActiveCfg = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|x64.Build.0 = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|x86.ActiveCfg = Release|Any CPU
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5}.Release|x86.Build.0 = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|x64.ActiveCfg = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|x64.Build.0 = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|x86.ActiveCfg = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Debug|x86.Build.0 = Debug|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|Any CPU.Build.0 = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|x64.ActiveCfg = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|x64.Build.0 = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|x86.ActiveCfg = Release|Any CPU
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9E2AC590-0841-4E4D-A9D7-F6E51F69AFF0} = {E5F5D5D6-1234-5678-9ABC-DEF123456789}
{0DC8A00C-5E88-47BF-9F05-B9666A0B49A5} = {F6A7B8C9-2345-6789-ABCD-EF2345678901}
{15DE9C10-6FA2-43BE-AA82-A77CD00C8D5D} = {5D20AA90-6969-D8BD-9DCD-8634F4692FDA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5D7E9F1-3456-7890-CDEF-123456789012}
EndGlobalSection
EndGlobal
EndGlobal
55 changes: 27 additions & 28 deletions samples/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# SharpSync Examples
# SharpSync Samples

This directory contains example code demonstrating how to use the SharpSync library.
This directory contains sample applications demonstrating how to use the SharpSync library.

## BasicSyncExample.cs
## Available Samples

A comprehensive example showing:
### 1. SharpSync.Samples.Console

A comprehensive console application that demonstrates most SharpSync features in an interactive menu-driven interface.

**Features demonstrated:**

- **Basic sync setup** - Creating storage, database, filter, and sync engine
- **Progress events** - Wiring up UI updates during sync
Expand All @@ -16,32 +20,18 @@ A comprehensive example showing:
- **Bandwidth throttling** - Limiting transfer speeds
- **Sync options** - Configuring `ChecksumOnly`, `SizeOnly`, `PreserveTimestamps`, `PreservePermissions`, `FollowSymlinks`, `ExcludePatterns`, `TimeoutSeconds`, `UpdateExisting`, `ConflictResolution` override, and `Verbose` logging
- **Smart conflict resolution** - Handling conflicts with UI prompts
- **OAuth2 authentication** - Browser-based OAuth2 flow for Nextcloud/OCIS

## ConsoleOAuth2Example.cs

A reference implementation of `IOAuth2Provider` for console/headless applications:

- **Browser-based OAuth2 flow** - Opens the system browser and listens on localhost for the callback
- **Authorization code exchange** - Exchanges the code for access and refresh tokens
- **Token refresh** - Refreshing expired tokens using the refresh token
- **Token validation** - Checking token validity before API calls
- **Nextcloud integration** - End-to-end example connecting to Nextcloud via WebDAV with OAuth2
- **Cross-platform browser launch** - Works on Windows, macOS, and Linux

## Usage

This is a standalone example file, not a buildable project. To use it:
**To run:**
```bash
cd samples/SharpSync.Samples.Console
dotnet run
```

1. Create a new .NET 8.0+ project
2. Add the SharpSync NuGet package:
```bash
dotnet add package Oire.SharpSync
```
3. Optionally add logging:
```bash
dotnet add package Microsoft.Extensions.Logging.Console
```
4. Copy the relevant code from `BasicSyncExample.cs` into your project
The application will present an interactive menu with options to:
1. Run a basic local-to-local sync demo using temporary directories
2. View an overview of all available `SyncOptions`
3. Run the OAuth2 Nextcloud sync example (requires a live Nextcloud server)

## Storage Options

Expand Down Expand Up @@ -86,3 +76,12 @@ foreach (var op in history) {
Console.WriteLine($"{op.ActionType}: {op.Path} ({op.Duration.TotalSeconds:F1}s)");
}
```

## Adding Your Own Samples

Feel free to contribute additional samples demonstrating specific use cases:

- Desktop application integration (WPF/WinUI/Avalonia)
- Background service for scheduled sync
- S3 or SFTP sync workflows
- Custom conflict resolution strategies
Loading