Skip to content

feat: storage provider abstraction + FTP provider#168

Merged
phmatray merged 26 commits into
devfrom
storage-providers
Jun 2, 2026
Merged

feat: storage provider abstraction + FTP provider#168
phmatray merged 26 commits into
devfrom
storage-providers

Conversation

@phmatray
Copy link
Copy Markdown
Contributor

@phmatray phmatray commented Jun 2, 2026

Summary

Introduces a storage-provider abstraction so the VFS can load from / write to multiple backends, migrates the existing GitHub support onto it with no behavior change, and adds a read+write FTP provider with a capability-driven Blazor UI. Built per the design spec & plan under docs/superpowers/ (merged via #167).

  • Atypical.VirtualFileSystem.Providers.Abstractions (new): IStorageProvider, IStorageProviderAuth, ProviderCapabilities, and neutral records. Branch/PR/fork are capability flags + CommitContext fields (not base-interface methods), so providers that can't do PRs (FTP) simply ignore them.
  • GitHub migration: GitHubStorageProvider/GitHubProviderAuth/adapters wrap the existing Octokit loader & write service — the original 71 GitHub tests still pass (regression gate).
  • Atypical.VirtualFileSystem.Ftp (new): FtpStorageProvider over FluentFTP behind an IFtpConnection seam (TDD against an in-memory fake) — recursive import with size filtering, read, and write (per-file overwrite/delete with partial-success results), leak-safe connect/disconnect, TLS opt-in.
  • Blazor: IStorageProviderRegistry selects the active provider; a capability-driven import dialog (provider picker) and capability-gated "submit changes" (PR for GitHub, upload for FTP). GitHub import/PR flows preserved unchanged; FTP credentials persisted encrypted via ProtectedLocalStorage.

Each phase was implemented test-first and passed spec + code-quality review.

Follow-ups (out of scope, tracked)

  • FTP write-back change-tracking (the upload path is plumbed + tested; the change-set tracker is deferred).
  • Route GitHub through the neutral provider to retire the legacy GitHub-named services.
  • Apply extension/glob filters in FTP import; rename GitHubImportDialogStorageImportDialog.
  • OneDrive (Microsoft Graph + OAuth) — see issue [REVENUE] Implement cloud backend adapters (S3, Azure Blob, GCS) #132.

Test plan

  • dotnet build Atypical.VirtualFileSystem.sln -c Release — 0 errors
  • dotnet test — 970 test runs pass on net9.0 + net10.0 (399 Core + 76 GitHub + 5 Abstractions + 5 FTP, ×2 TFMs)
  • DemoBlazorApp starts cleanly; GitHub flow unchanged, FTP provider selectable & imports

🤖 Generated with Claude Code

Philippe Matray and others added 26 commits June 2, 2026 11:54
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lter), not TargetPath

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, TLS hardening

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aming

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a provider <select> at the top of the import dialog driven by
IStorageProviderRegistry, and render the field set from the active
provider's capabilities. GitHub (SupportsBranches) keeps the existing
owner/repo/branch/subpath fields and GitHubImportService flow unchanged;
credentials providers (FTP) get host/port/username/password/useTls/base-path
fields, authenticate via Registry.Active.Auth, persist via
StorageCredentialStore, and import via StorageImportService.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add StoragePendingChangesService exposing SupportsPullRequests (from the
active provider's capabilities) and SubmitAsync, which routes ProviderFileChange
sets to the active provider's WriteChangesAsync. Gate CreatePullRequestDialog by
capability: GitHub (SupportsPullRequests) keeps the existing fork->branch->commit->PR
flow and "Create Pull Request" button untouched; non-PR providers (FTP) show an
"Upload changes" confirmation view and button that calls SubmitAsync.

Note: FTP change-set tracking from VFS edits is not yet wired (no tracker
populates changes for FTP-imported files), so the upload list is empty today;
the capability gating, labelling, and service plumbing are in place for when
that tracking lands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Throw InvalidOperationException from StoragePendingChangesService.SubmitAsync
when the active provider supports pull requests, turning a future mis-call into
a clear contract violation instead of WriteChangesAsync's NotSupportedException.

Marshal the bare StateHasChanged() calls in OnProviderChanged and StartFtpImport
through InvokeAsync for consistency with the other awaited Dispatcher calls.

Document that FTP credential fields are intentionally not cleared on Close so
they re-prefill from the credential store next time, unlike the GitHub fields.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Storage Providers section to CLAUDE.md (IStorageProvider abstraction,
capability-driven branch/PR handling, GitHub + FTP implementations, the Blazor
registry/UI), and correct the plan's misleading subpath-mapping comment to
reflect the implemented SubPath (remote filter) vs TargetPath (VFS destination).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@phmatray phmatray merged commit 11a1351 into dev Jun 2, 2026
1 check passed
@phmatray phmatray deleted the storage-providers branch June 2, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant