-
Notifications
You must be signed in to change notification settings - Fork 20
feat: infrastructure for publisher catalogs and enhanced acquisition tracking #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: development
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| using GenHub.Core.Models.Content; | ||
| using GenHub.Core.Models.Enums; | ||
|
|
||
| namespace GenHub.Core.Extensions; | ||
|
|
||
| /// <summary> | ||
| /// Extension methods for ContentAcquisitionProgress. | ||
| /// </summary> | ||
| public static class ContentAcquisitionProgressExtensions | ||
| { | ||
| /// <summary> | ||
| /// Formats a user-friendly progress status message with stage indicators. | ||
| /// </summary> | ||
| /// <param name="progress">The progress object to format.</param> | ||
| /// <returns>A formatted progress status string.</returns> | ||
| public static string FormatProgressStatus(this ContentAcquisitionProgress progress) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(progress); | ||
|
|
||
| // Use the new staged progress format if available | ||
| if (progress.TotalStages > 0 && progress.CurrentStage > 0) | ||
| { | ||
| var stagePart = $"{progress.CurrentStage}/{progress.TotalStages}"; | ||
| var description = !string.IsNullOrEmpty(progress.StageDescription) | ||
| ? progress.StageDescription | ||
| : progress.CurrentOperation; | ||
|
|
||
| // Add percentage for stages that have measurable progress | ||
| var percentPart = progress.StageProgress > 0 && progress.StageProgress < 100 | ||
| ? $" ({progress.StageProgress:F0}%)" | ||
| : string.Empty; | ||
|
|
||
| // Add bottleneck indicator if applicable | ||
| var bottleneckPart = progress.IsBottleneck && !string.IsNullOrEmpty(progress.BottleneckReason) | ||
| ? $" - {progress.BottleneckReason}" | ||
| : string.Empty; | ||
|
|
||
| // Add file count if processing multiple files | ||
| var filesPart = progress.TotalFiles > 1 | ||
| ? $" [{progress.FilesProcessed}/{progress.TotalFiles}]" | ||
| : string.Empty; | ||
|
|
||
| return $"{stagePart} - {description}{percentPart}{filesPart}{bottleneckPart}"; | ||
| } | ||
|
|
||
| // Fallback to phase-based format | ||
| var phaseName = progress.Phase switch | ||
| { | ||
| ContentAcquisitionPhase.Downloading => "Downloading", | ||
| ContentAcquisitionPhase.Extracting => "Extracting", | ||
| ContentAcquisitionPhase.Copying => "Copying", | ||
| ContentAcquisitionPhase.ValidatingManifest => "Validating manifest", | ||
| ContentAcquisitionPhase.ValidatingFiles => "Validating files", | ||
| ContentAcquisitionPhase.Delivering => "Installing", | ||
| ContentAcquisitionPhase.Completed => "Complete", | ||
| _ => "Processing", | ||
| }; | ||
|
|
||
| if (!string.IsNullOrEmpty(progress.CurrentOperation)) | ||
| { | ||
| return $"{phaseName}: {progress.CurrentOperation}"; | ||
| } | ||
|
|
||
| var percentText = progress.ProgressPercentage > 0 ? $"{progress.ProgressPercentage:F0}%" : string.Empty; | ||
|
|
||
| if (progress.TotalFiles > 0) | ||
| { | ||
| var phasePercent = progress.TotalFiles > 0 | ||
| ? (int)((double)progress.FilesProcessed / progress.TotalFiles * 100) | ||
| : 0; | ||
| return $"{phaseName}: {progress.FilesProcessed}/{progress.TotalFiles} files ({phasePercent}%)"; | ||
| } | ||
|
|
||
| return !string.IsNullOrEmpty(percentText) ? $"{phaseName}... {percentText}" : $"{phaseName}..."; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| using GenHub.Core.Models.Providers; | ||
| using GenHub.Core.Models.Results; | ||
|
|
||
| namespace GenHub.Core.Interfaces.Providers; | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| /// <summary> | ||
| /// Parses publisher catalog JSON into structured models. | ||
| /// </summary> | ||
| public interface IPublisherCatalogParser | ||
| { | ||
| /// <summary> | ||
| /// Parses a catalog from JSON content. | ||
| /// </summary> | ||
| /// <param name="catalogJson">The raw JSON content of the catalog.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>The parsed catalog or an error.</returns> | ||
| Task<OperationResult<PublisherCatalog>> ParseCatalogAsync(string catalogJson, CancellationToken cancellationToken = default); | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
| /// <summary> | ||
| /// Validates that a catalog conforms to the expected schema version. | ||
| /// </summary> | ||
| /// <param name="catalog">The catalog to validate.</param> | ||
| /// <returns>Validation result with any errors.</returns> | ||
| OperationResult<bool> ValidateCatalog(PublisherCatalog catalog); | ||
|
|
||
| /// <summary> | ||
| /// Verifies the catalog signature if present. | ||
| /// </summary> | ||
| /// <param name="catalogJson">The raw JSON content.</param> | ||
| /// <param name="catalog">The parsed catalog with signature field.</param> | ||
| /// <returns>True if signature is valid or not required.</returns> | ||
| bool VerifySignature(string catalogJson, PublisherCatalog catalog); | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,24 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using GenHub.Core.Models.Results; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace GenHub.Core.Interfaces.Providers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Service for refreshing subscribed publisher catalogs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public interface IPublisherCatalogRefreshService | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Refreshes all subscribed catalogs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>Summary of the refresh operation.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task<OperationResult<bool>> RefreshAllAsync(CancellationToken cancellationToken = default); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Refreshes a specific publisher's catalog. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="publisherId">The publisher identifier.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="cancellationToken">Cancellation token.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <returns>True if refreshed, false otherwise.</returns> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Task<OperationResult<bool>> RefreshPublisherAsync(string publisherId, CancellationToken cancellationToken = default); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+10
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove duplicate XML doc blocks to avoid XML doc warnings. Both methods repeat 🧹 Example cleanup (apply to both methods)- /// <summary>
- /// Refreshes all subscribed catalogs.
- /// </summary>
- /// <param name="cancellationToken">Cancellation token.</param>
- /// <summary>
- /// Refreshes all subscribed publisher catalogs.
- /// </summary>
- /// <param name="cancellationToken">Token to cancel the refresh operation.</param>
- /// <returns>An <see cref="OperationResult{T}"/> containing `true` if the refresh succeeded, `false` otherwise.</returns>
+ /// <summary>
+ /// Refreshes all subscribed catalogs.
+ /// </summary>
+ /// <param name="cancellationToken">Cancellation token.</param>
+ /// <returns>An <see cref="OperationResult{T}"/> containing `true` if the refresh succeeded, `false` otherwise.</returns>
Task<OperationResult<bool>> RefreshAllAsync(CancellationToken cancellationToken = default);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| using GenHub.Core.Models.Enums; | ||
| using GenHub.Core.Models.Providers; | ||
| using GenHub.Core.Models.Results; | ||
|
|
||
| namespace GenHub.Core.Interfaces.Providers; | ||
|
|
||
| /// <summary> | ||
| /// Manages user subscriptions to publisher catalogs. | ||
| /// Subscriptions are stored locally and enable discovery of creator content. | ||
| /// </summary> | ||
| public interface IPublisherSubscriptionStore | ||
| { | ||
| /// <summary> | ||
| /// Gets all active publisher subscriptions. | ||
| /// </summary> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>List of active subscriptions.</returns> | ||
| Task<OperationResult<IEnumerable<PublisherSubscription>>> GetSubscriptionsAsync(CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Gets a specific subscription by publisher ID. | ||
| /// </summary> | ||
| /// <param name="publisherId">The publisher identifier.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>The subscription if found, null otherwise.</returns> | ||
| Task<OperationResult<PublisherSubscription?>> GetSubscriptionAsync(string publisherId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Adds a new publisher subscription. | ||
| /// </summary> | ||
| /// <param name="subscription">The subscription to add.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>Operation result indicating success or failure.</returns> | ||
| Task<OperationResult<bool>> AddSubscriptionAsync(PublisherSubscription subscription, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Removes a publisher subscription. | ||
| /// </summary> | ||
| /// <param name="publisherId">The publisher identifier to remove.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>Operation result indicating success or failure.</returns> | ||
| Task<OperationResult<bool>> RemoveSubscriptionAsync(string publisherId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Updates an existing subscription. | ||
| /// </summary> | ||
| /// <param name="subscription">The updated subscription data.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>Operation result indicating success or failure.</returns> | ||
| Task<OperationResult<bool>> UpdateSubscriptionAsync(PublisherSubscription subscription, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Checks if a publisher subscription exists. | ||
| /// </summary> | ||
| /// <param name="publisherId">The publisher identifier.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>True if subscribed, false otherwise.</returns> | ||
| Task<OperationResult<bool>> IsSubscribedAsync(string publisherId, CancellationToken cancellationToken = default); | ||
|
|
||
| /// <summary> | ||
| /// Updates the trust level for a publisher. | ||
| /// </summary> | ||
| /// <param name="publisherId">The publisher identifier.</param> | ||
| /// <param name="trustLevel">The new trust level.</param> | ||
| /// <param name="cancellationToken">Cancellation token.</param> | ||
| /// <returns>Operation result indicating success or failure.</returns> | ||
| Task<OperationResult<bool>> UpdateTrustLevelAsync(string publisherId, TrustLevel trustLevel, CancellationToken cancellationToken = default); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| using GenHub.Core.Models.Providers; | ||
|
|
||
| namespace GenHub.Core.Interfaces.Providers; | ||
|
|
||
| /// <summary> | ||
| /// Filters content releases based on version display policy. | ||
| /// </summary> | ||
| public interface IVersionSelector | ||
| { | ||
| /// <summary> | ||
| /// Selects releases based on the specified policy. | ||
| /// </summary> | ||
| /// <param name="releases">All available releases.</param> | ||
| /// <param name="policy">The version selection policy.</param> | ||
| /// <returns>Filtered releases according to policy.</returns> | ||
| IEnumerable<ContentRelease> SelectReleases(IEnumerable<ContentRelease> releases, VersionPolicy policy); | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest stable release from a collection. | ||
| /// </summary> | ||
| /// <param name="releases">All available releases.</param> | ||
| /// <returns>The latest stable release, or null if none exist.</returns> | ||
| ContentRelease? GetLatestStable(IEnumerable<ContentRelease> releases); | ||
|
|
||
| /// <summary> | ||
| /// Gets the latest release (including prereleases) from a collection. | ||
| /// </summary> | ||
| /// <param name="releases">All available releases.</param> | ||
| /// <returns>The latest release, or null if none exist.</returns> | ||
| ContentRelease? GetLatest(IEnumerable<ContentRelease> releases); | ||
|
coderabbitai[bot] marked this conversation as resolved.
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| namespace GenHub.Core.Interfaces.Providers; | ||
|
|
||
| /// <summary> | ||
| /// Defines the version filtering policy for content display. | ||
| /// </summary> | ||
| public enum VersionPolicy | ||
| { | ||
| /// <summary> | ||
| /// Show only the latest stable release (default). | ||
| /// </summary> | ||
| LatestStableOnly, | ||
|
|
||
| /// <summary> | ||
| /// Show all versions including older releases. | ||
| /// </summary> | ||
| AllVersions, | ||
|
|
||
| /// <summary> | ||
| /// Include prerelease versions in addition to stable releases. | ||
| /// </summary> | ||
| IncludePrereleases, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using CommunityToolkit.Mvvm.Messaging.Messages; | ||
|
|
||
| namespace GenHub.Core.Messages; | ||
|
|
||
| /// <summary> | ||
| /// Message sent when a user wants to close the content details view. | ||
|
undead2146 marked this conversation as resolved.
|
||
| /// </summary> | ||
| public class CloseContentDetailMessage() : ValueChangedMessage<bool>(true) | ||
| { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| using CommunityToolkit.Mvvm.Messaging.Messages; | ||
|
|
||
| namespace GenHub.Core.Messages; | ||
|
|
||
| /// <summary> | ||
| /// Message sent when a user wants to close the publisher details view and return to the dashboard. | ||
|
undead2146 marked this conversation as resolved.
|
||
| /// </summary> | ||
| public class ClosePublisherDetailsMessage() : ValueChangedMessage<bool>(true) | ||
| { | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| using CommunityToolkit.Mvvm.Messaging.Messages; | ||
|
|
||
| namespace GenHub.Core.Messages; | ||
|
|
||
| /// <summary> | ||
| /// Message sent when a user wants to view details for a specific publisher. | ||
| /// </summary> | ||
| /// <param name="publisherId">The ID of the publisher to view.</param> | ||
| public class OpenPublisherDetailsMessage(string publisherId) : ValueChangedMessage<string>(publisherId) | ||
| { | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.