From 99e5788ddf9d4000f2f40373d76d5d7f01c78794 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 1 Mar 2023 14:38:23 +0100 Subject: [PATCH] New streaming method. --- .../Directory.Build.props | 2 +- .../Squidex.ClientLibrary/ContentsClient.cs | 62 +++++++++++++++++++ .../Squidex.ClientLibrary/IContentsClient.cs | 16 ++++- .../Squidex.ClientLibrary/QueryContext.cs | 25 ++++++++ .../Utils/SquidexClientBase.cs | 21 ++++--- 5 files changed, 115 insertions(+), 11 deletions(-) diff --git a/csharp/Squidex.ClientLibrary/Directory.Build.props b/csharp/Squidex.ClientLibrary/Directory.Build.props index d59c3a21..c57b54a8 100644 --- a/csharp/Squidex.ClientLibrary/Directory.Build.props +++ b/csharp/Squidex.ClientLibrary/Directory.Build.props @@ -15,7 +15,7 @@ Squidex HeadlessCMS true snupkg - 14.1.0 + 14.2.0 diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs index 43e8ba64..819bc538 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/ContentsClient.cs @@ -82,6 +82,68 @@ public async Task GetAllAsync(Func callback, int batchSize = 200, while (!ct.IsCancellationRequested); } + /// + public async Task StreamAllAsync(Func callback, int skip = 0, QueryContext? context = null, + CancellationToken ct = default) + { + Guard.Between(skip, 0, int.MaxValue, nameof(skip)); + Guard.NotNull(callback, nameof(callback)); + + context = (context ?? QueryContext.Default).WithoutTotal(); + + var httpClient = HttpClientProvider.Get(); + try + { + using (var request = BuildRequest(HttpMethod.Get, BuildUrl($"stream?skip={skip}", false), null, context)) + { + using (var response = await httpClient.SendAsync(request, ct)) + { + await EnsureResponseIsValidAsync(response); + +#if NETSTANDARD2_0 || NETCOREAPP3_1 + using (var reader = new StreamReader(await response.Content.ReadAsStreamAsync())) +#else + using (var reader = new StreamReader(await response.Content.ReadAsStreamAsync(ct))) +#endif + { + string? line; +#if NET7_0_OR_GREATER + while ((line = await reader.ReadLineAsync(ct)) != null) +#else + while ((line = await reader.ReadLineAsync()) != null) +#endif + { + ct.ThrowIfCancellationRequested(); + + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + const string Prefix = "data: "; + + if (!line.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) + { + throw new SquidexException("Line does not start with data prefix.", 0, null); + } + +#pragma warning disable IDE0057 // Use range operator + var contentJson = line.Substring(Prefix.Length); +#pragma warning restore IDE0057 // Use range operator + var contentItem = contentJson.FromJson(); + + await callback(contentItem); + } + } + } + } + } + finally + { + HttpClientProvider.Return(httpClient); + } + } + /// public Task> GetAsync(ContentQuery? query = null, QueryContext? context = null, CancellationToken ct = default) diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/IContentsClient.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/IContentsClient.cs index 69d39572..10dcbc8e 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/IContentsClient.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/IContentsClient.cs @@ -271,7 +271,7 @@ Task> BulkUpdateAsync(BulkUpdate update, /// /// Gets all content items in batches. /// - /// The callbac that is invoked for each content item. + /// The callback that is invoked for each content item. /// Size of each batch. /// The context object to add additonal headers to the request and change the behavior of the API when querying content items. /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. @@ -282,6 +282,20 @@ Task> BulkUpdateAsync(BulkUpdate update, Task GetAllAsync(Func callback, int batchSize = 200, QueryContext? context = null, CancellationToken ct = default); + /// + /// Gets all content items in batches. + /// + /// The callback that is invoked for each content item. + /// The items to skip. + /// The context object to add additonal headers to the request and change the behavior of the API when querying content items. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// The task for completion. + /// + /// is null. + Task StreamAllAsync(Func callback, int skip = 0, QueryContext? context = null, + CancellationToken ct = default); + /// /// Gets a content item by ID. /// diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/QueryContext.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/QueryContext.cs index eeb7394c..d9629971 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/QueryContext.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/QueryContext.cs @@ -56,6 +56,14 @@ public sealed class QueryContext /// public bool IsUnpublished { get; private set; } + /// + /// Gets a value indicating whether scripting will be skipped. + /// + /// + /// true if scripting will be skipped; otherwise, false. + /// + public bool IsNotScripting { get; private set; } + /// /// Gets a value indicating whether the Content CDN will not be used. /// @@ -128,6 +136,18 @@ public QueryContext WithoutCDN(bool value = true) return Clone(c => c.IsNotUsingCDN = value); } + /// + /// Creates a new copy of the context object and defines whether scripting will be skipped. + /// + /// if set to true scripting will be skipped. + /// + /// The new query context. + /// + public QueryContext WithoutScripting(bool value = true) + { + return Clone(c => c.IsNotScripting = value); + } + /// /// Creates a new copy of the context object and defines whether the total number of results should not be returned. /// @@ -216,6 +236,11 @@ internal void AddToHeaders(HttpRequestHeaders headers) headers.TryAddWithoutValidation("X-NoSlowTotal", "true"); } + if (IsNotScripting) + { + headers.TryAddWithoutValidation("X-NoScripting", "true"); + } + if (Languages != null) { var languages = string.Join(", ", Languages.Where(x => !string.IsNullOrWhiteSpace(x))); diff --git a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Utils/SquidexClientBase.cs b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Utils/SquidexClientBase.cs index 8d1afc8e..4d0bb14f 100644 --- a/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Utils/SquidexClientBase.cs +++ b/csharp/Squidex.ClientLibrary/Squidex.ClientLibrary/Utils/SquidexClientBase.cs @@ -16,8 +16,6 @@ namespace Squidex.ClientLibrary.Utils; /// public abstract class SquidexClientBase { - private readonly IHttpClientProvider httpClientProvider; - /// /// Gets the name of the App. /// @@ -34,6 +32,11 @@ public abstract class SquidexClientBase /// public SquidexOptions Options { get; } + /// + /// The http client provider. + /// + protected IHttpClientProvider HttpClientProvider { get; } + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member protected internal SquidexClientBase(SquidexOptions options, string appName, IHttpClientProvider httpClientProvider) { @@ -47,13 +50,13 @@ protected internal SquidexClientBase(SquidexOptions options, string appName, IHt // Just pass in the options to have direct access to them. Options = options; - this.httpClientProvider = httpClientProvider; + HttpClientProvider = httpClientProvider; } protected internal async Task RequestAsync(HttpMethod method, string path, HttpContent? content, QueryContext? context, CancellationToken ct) { - var httpClient = httpClientProvider.Get(); + var httpClient = HttpClientProvider.Get(); try { using (var request = BuildRequest(method, path, content, context)) @@ -66,14 +69,14 @@ protected internal async Task RequestAsync(HttpMethod method, string path, HttpC } finally { - httpClientProvider.Return(httpClient); + HttpClientProvider.Return(httpClient); } } protected internal async Task RequestStreamAsync(HttpMethod method, string path, HttpContent? content, QueryContext? context, CancellationToken ct) { - var httpClient = httpClientProvider.Get(); + var httpClient = HttpClientProvider.Get(); try { using (var request = BuildRequest(method, path, content, context)) @@ -90,14 +93,14 @@ protected internal async Task RequestStreamAsync(HttpMethod method, stri } finally { - httpClientProvider.Return(httpClient); + HttpClientProvider.Return(httpClient); } } protected internal async Task RequestJsonAsync(HttpMethod method, string path, HttpContent? content, QueryContext? context, CancellationToken ct) { - var httpClient = httpClientProvider.Get(); + var httpClient = HttpClientProvider.Get(); try { using (var request = BuildRequest(method, path, content, context)) @@ -112,7 +115,7 @@ protected internal async Task RequestJsonAsync(HttpMethod method, string p } finally { - httpClientProvider.Return(httpClient); + HttpClientProvider.Return(httpClient); } }