diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4be1964f..2ff095b9 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,11 +1,11 @@
name: Pixeval Build Pipeline
on:
- push:
- branches-ignore:
- - 'dependabot**'
- tags-ignore:
- - '**'
+ # push:
+ # branches-ignore:
+ # - 'dependabot**'
+ # tags-ignore:
+ # - '**'
pull_request:
paths-ignore:
- '**.md'
@@ -23,9 +23,9 @@ jobs:
runs-on: windows-latest
strategy:
- fail-fast: false
+ fail-fast: true
matrix:
- configuration: [Debug]
+ configuration: [Release]
platform: ['x64','x86','arm64']
env:
@@ -70,8 +70,8 @@ jobs:
shell: pwsh
run: 'nuget restore $env:SOLUTION_NAME'
- - name: Build Files (Debug)
- id: build_app_with_debug
+ - name: Build Files (Release)
+ id: build_app_with_release
shell: pwsh
run: |
msbuild $env:PACKAGE_PROJECT_NAME `
diff --git a/.gitignore b/.gitignore
index c631a3ad..4c10ab12 100644
--- a/.gitignore
+++ b/.gitignore
@@ -184,7 +184,7 @@ publish/
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
+# *.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
diff --git a/Pixeval.sln b/Pixeval.sln
index 60d281ad..85580c45 100644
--- a/Pixeval.sln
+++ b/Pixeval.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31808.319
diff --git a/Pixeval.slnx b/Pixeval.slnx
index a161cdb3..a7edfd9b 100644
--- a/Pixeval.slnx
+++ b/Pixeval.slnx
@@ -35,23 +35,19 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/Pixeval.Controls/AdvancedItemsView/ItemsViewLayoutType.cs b/src/Pixeval.Controls/AdvancedItemsView/ItemsViewLayoutType.cs
index ea579ceb..f9551234 100644
--- a/src/Pixeval.Controls/AdvancedItemsView/ItemsViewLayoutType.cs
+++ b/src/Pixeval.Controls/AdvancedItemsView/ItemsViewLayoutType.cs
@@ -24,6 +24,7 @@ using Pixeval.Attributes;
namespace Pixeval.Controls;
+[LocalizationMetadata(typeof(AdvancedItemsViewResources))]
public enum ItemsViewLayoutType
{
[LocalizedResource(typeof(AdvancedItemsViewResources), nameof(AdvancedItemsViewResources.LinedFlow))]
diff --git a/src/Pixeval.Controls/AdvancedObservableCollection.cs b/src/Pixeval.Controls/AdvancedObservableCollection.cs
index b09850e9..c7a7977f 100644
--- a/src/Pixeval.Controls/AdvancedObservableCollection.cs
+++ b/src/Pixeval.Controls/AdvancedObservableCollection.cs
@@ -33,12 +33,13 @@ using Windows.Foundation;
using CommunityToolkit.WinUI.Collections;
using CommunityToolkit.WinUI.Helpers;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
namespace Pixeval.Collections;
[DebuggerDisplay("Count = {Count}")]
-public class AdvancedObservableCollection : IList, IList, IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer where T : class
+public class AdvancedObservableCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : IList, IList, IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer where T : class
{
private readonly Dictionary _sortProperties = [];
diff --git a/src/Pixeval.Controls/ColorHelper.cs b/src/Pixeval.Controls/Attributes/LocalizationMetadataAttribute.cs
similarity index 54%
rename from src/Pixeval.Controls/ColorHelper.cs
rename to src/Pixeval.Controls/Attributes/LocalizationMetadataAttribute.cs
index 6118b538..58800919 100644
--- a/src/Pixeval.Controls/ColorHelper.cs
+++ b/src/Pixeval.Controls/Attributes/LocalizationMetadataAttribute.cs
@@ -1,9 +1,8 @@
#region Copyright
-
// GPL v3 License
//
-// Pixeval/Pixeval.Controls
-// Copyright (c) 2024 Pixeval.Controls/ColorHelper.cs
+// Pixeval/Pixeval
+// Copyright (c) 2024 Pixeval/EnumLocalizationMetadataAttribute.cs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -17,31 +16,23 @@
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-
#endregion
-using Windows.UI;
+using System;
-namespace Pixeval.Controls;
+namespace Pixeval.Attributes;
-public static class ColorHelper
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum)]
+public class LocalizationMetadataAttribute(Type resourceType) : Attribute
{
- public static unsafe Color GetAlphaColor(this uint color)
- {
- var ptr = &color;
- var c = (byte*)ptr;
- return Color.FromArgb(c[3], c[2], c[1], c[0]);
- }
+ public Type ResourceType { get; } = resourceType;
- public static unsafe uint GetAlphaUInt(this Color color)
- {
- uint ret;
- var ptr = &ret;
- var c = (byte*)ptr;
- c[0] = color.B;
- c[1] = color.G;
- c[2] = color.R;
- c[3] = color.A;
- return ret;
- }
+ public bool IsPartial { get; init; }
+}
+
+
+[AttributeUsage(AttributeTargets.Class)]
+public class AttachedLocalizationMetadataAttribute(Type resourceType) : Attribute
+{
+ public Type ResourceType { get; } = resourceType;
}
diff --git a/src/Pixeval.Controls/Attributes/LocalizedResourceAttribute.cs b/src/Pixeval.Controls/Attributes/LocalizedResourceAttribute.cs
index ef36261e..63de848e 100644
--- a/src/Pixeval.Controls/Attributes/LocalizedResourceAttribute.cs
+++ b/src/Pixeval.Controls/Attributes/LocalizedResourceAttribute.cs
@@ -24,7 +24,7 @@ using System;
namespace Pixeval.Attributes;
[AttributeUsage(AttributeTargets.Field)]
-public class LocalizedResource(Type resourceLoader, string key, object? formatKey = null) : Attribute
+public class LocalizedResourceAttribute(Type resourceLoader, string key, object? formatKey = null) : Attribute
{
public Type ResourceLoader { get; } = resourceLoader;
@@ -32,3 +32,11 @@ public class LocalizedResource(Type resourceLoader, string key, object? formatKe
public object? FormatKey { get; } = formatKey;
}
+
+[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
+public class AttachedLocalizedResourceAttribute(string fieldName, string key) : Attribute
+{
+ public string FieldName { get; } = fieldName;
+
+ public string Key { get; } = key;
+}
diff --git a/src/Pixeval.Controls/C.cs b/src/Pixeval.Controls/C.cs
index dab6a6aa..39c99944 100644
--- a/src/Pixeval.Controls/C.cs
+++ b/src/Pixeval.Controls/C.cs
@@ -62,7 +62,7 @@ public static class C
return Color.FromArgb(c[3], c[2], c[1], c[0]);
}
- public static SolidColorBrush ToSolidColorBrush(uint value) => new(value.GetAlphaColor());
+ public static SolidColorBrush ToSolidColorBrush(uint value) => new(ToAlphaColor(value));
public static unsafe uint ToAlphaUInt(Color color)
{
diff --git a/src/Pixeval.Controls/Pixeval.Controls.csproj b/src/Pixeval.Controls/Pixeval.Controls.csproj
index ff8f0faa..1fce51e6 100644
--- a/src/Pixeval.Controls/Pixeval.Controls.csproj
+++ b/src/Pixeval.Controls/Pixeval.Controls.csproj
@@ -7,9 +7,11 @@
win-x86;win-x64;win-arm64
true
Enable
+ Debug;Release
preview
true
zh-cn
+ true
@@ -17,20 +19,21 @@
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
-
+
+
diff --git a/src/Pixeval/Controls/StringRepresentableItem.cs b/src/Pixeval.Controls/StringRepresentableItem.cs
similarity index 100%
rename from src/Pixeval/Controls/StringRepresentableItem.cs
rename to src/Pixeval.Controls/StringRepresentableItem.cs
diff --git a/src/Pixeval.CoreApi/AppJsonSerializerContext.cs b/src/Pixeval.CoreApi/AppJsonSerializerContext.cs
index a20faf4b..8fa9743f 100644
--- a/src/Pixeval.CoreApi/AppJsonSerializerContext.cs
+++ b/src/Pixeval.CoreApi/AppJsonSerializerContext.cs
@@ -20,11 +20,14 @@
#endregion
+using System.Text.Json;
+using System;
using System.Text.Json.Serialization;
using Pixeval.CoreApi.Global.Enum;
using Pixeval.CoreApi.Model;
using Pixeval.CoreApi.Net.Request;
using Pixeval.CoreApi.Net.Response;
+using Pixeval.CoreApi.Preference;
namespace Pixeval.CoreApi;
@@ -32,7 +35,7 @@ namespace Pixeval.CoreApi;
[JsonSerializable(typeof(PixivBookmarkTagResponse))]
[JsonSerializable(typeof(PixivCommentResponse))]
[JsonSerializable(typeof(PixivIllustrationResponse))]
-[JsonSerializable(typeof(PixivNextUrlResponse<>))]
+// [JsonSerializable(typeof(PixivNextUrlResponse<>))]
[JsonSerializable(typeof(PixivNovelResponse))]
[JsonSerializable(typeof(PixivRelatedUsersResponse))]
[JsonSerializable(typeof(PixivSingleIllustResponse))]
@@ -138,4 +141,32 @@ namespace Pixeval.CoreApi;
[JsonSerializable(typeof(WorkSortOption))]
[JsonSerializable(typeof(WorkType))]
[JsonSerializable(typeof(SimpleWorkType))]
-internal partial class AppJsonSerializerContext : JsonSerializerContext;
+
+[JsonSerializable(typeof(string[]))]
+[JsonSerializable(typeof(Tag[]))]
+[JsonSerializable(typeof(MetaPage[]))]
+[JsonSerializable(typeof(NovelIllustInfo[]))]
+[JsonSerializable(typeof(NovelImage[]))]
+[JsonSerializable(typeof(NovelReplaceableGlossary[]))]
+[JsonSerializable(typeof(long[]))]
+[JsonSerializable(typeof(NovelTag[]))]
+[JsonSerializable(typeof(BookmarkTag[]))]
+[JsonSerializable(typeof(Comment[]))]
+[JsonSerializable(typeof(Illustration[]))]
+[JsonSerializable(typeof(Novel[]))]
+[JsonSerializable(typeof(User[]))]
+[JsonSerializable(typeof(SpotlightBody[]))]
+[JsonSerializable(typeof(PixivisionTag[]))]
+[JsonSerializable(typeof(Illust[]))]
+[JsonSerializable(typeof(RelatedArticle[]))]
+[JsonSerializable(typeof(Spotlight[]))]
+[JsonSerializable(typeof(Result[]))]
+[JsonSerializable(typeof(TrendingTag[]))]
+[JsonSerializable(typeof(Frame[]))]
+[JsonSerializable(typeof(UserSpecifiedBookmarkTag[]))]
+[JsonSerializable(typeof(Work[]))]
+
+[JsonSerializable(typeof(Session))]
+public partial class AppJsonSerializerContext : JsonSerializerContext;
+
+public class SnakeCaseLowerEnumConverter() : JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower) where T : struct, Enum;
diff --git a/src/Pixeval.CoreApi/Engine/AbstractPixivAsyncEnumerator.cs b/src/Pixeval.CoreApi/Engine/AbstractPixivAsyncEnumerator.cs
index 59725153..e4efd53d 100644
--- a/src/Pixeval.CoreApi/Engine/AbstractPixivAsyncEnumerator.cs
+++ b/src/Pixeval.CoreApi/Engine/AbstractPixivAsyncEnumerator.cs
@@ -21,6 +21,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
+using System.Text.Json;
using System.Threading.Tasks;
using Pixeval.CoreApi.Global.Exception;
using Pixeval.CoreApi.Model;
@@ -43,7 +44,7 @@ namespace Pixeval.CoreApi.Engine;
/// The fetch engine
public abstract class AbstractPixivAsyncEnumerator(TFetchEngine pixivFetchEngine,
MakoApiKind apiKind) : IAsyncEnumerator
- where TFetchEngine : class, IFetchEngine
+ where TFetchEngine : class, IFetchEngine
where TEntity : class, IEntry
where TRawEntity : class
{
@@ -100,13 +101,12 @@ public abstract class AbstractPixivAsyncEnumerator.AsFailure(await MakoNetworkException.FromHttpResponseMessageAsync(responseMessage, MakoClient.Configuration.DomainFronting).ConfigureAwait(false));
}
- var result = (await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false)).FromJson();
+ var str = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
- return result is null
- ? Result.AsFailure()
- : ValidateResponse(result)
- ? Result.AsSuccess(result)
- : Result.AsFailure();
+ if (JsonSerializer.Deserialize(str, typeof(TRawEntity), AppJsonSerializerContext.Default) is TRawEntity result)
+ if (ValidateResponse(result))
+ return Result.AsSuccess(result);
+ return Result.AsFailure();
}
catch (Exception e)
{
diff --git a/src/Pixeval.CoreApi/Engine/RecursivePixivFetchEngine.cs b/src/Pixeval.CoreApi/Engine/RecursivePixivFetchEngine.cs
index ca95d637..0f0c3366 100644
--- a/src/Pixeval.CoreApi/Engine/RecursivePixivFetchEngine.cs
+++ b/src/Pixeval.CoreApi/Engine/RecursivePixivFetchEngine.cs
@@ -29,9 +29,9 @@ namespace Pixeval.CoreApi.Engine;
internal abstract class RecursivePixivAsyncEnumerator(TFetchEngine pixivFetchEngine, MakoApiKind makoApiKind)
: AbstractPixivAsyncEnumerator(pixivFetchEngine, makoApiKind)
- where TEntity : class, IEntry
+ where TEntity : class, IEntry
where TRawEntity : class
- where TFetchEngine : class, IFetchEngine
+ where TFetchEngine : class, IFetchEngine
{
private TRawEntity? RawEntity { get; set; }
@@ -112,9 +112,9 @@ internal static class RecursivePixivAsyncEnumerators
{
public abstract class BaseRecursivePixivAsyncEnumerator(TFetchEngine pixivFetchEngine, MakoApiKind makoApiKind, string initialUrl)
: RecursivePixivAsyncEnumerator(pixivFetchEngine, makoApiKind)
- where TEntity : class, IEntry
- where TRawEntity : PixivNextUrlResponse
- where TFetchEngine : class, IFetchEngine
+ where TEntity : class, IEntry
+ where TRawEntity : class, IPixivNextUrlResponse
+ where TFetchEngine : class, IFetchEngine
{
protected override string InitialUrl => initialUrl;
diff --git a/src/Pixeval.CoreApi/Global/Enum/PrivacyPolicy.cs b/src/Pixeval.CoreApi/Global/Enum/PrivacyPolicy.cs
index 428aa2ee..1ea180a3 100644
--- a/src/Pixeval.CoreApi/Global/Enum/PrivacyPolicy.cs
+++ b/src/Pixeval.CoreApi/Global/Enum/PrivacyPolicy.cs
@@ -19,7 +19,7 @@
#endregion
using System.ComponentModel;
-using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
namespace Pixeval.CoreApi.Global.Enum;
@@ -27,13 +27,12 @@ namespace Pixeval.CoreApi.Global.Enum;
/// The privacy policy of Pixiv, be aware that the option
/// is only permitted when the ID is pointing to yourself
///
+[JsonConverter(typeof(SnakeCaseLowerEnumConverter))]
public enum PrivacyPolicy
{
[Description("public")]
- [EnumMember(Value = "public")]
Public,
[Description("private")]
- [EnumMember(Value = "private")]
Private
}
diff --git a/src/Pixeval.CoreApi/Global/Enum/RankOption.cs b/src/Pixeval.CoreApi/Global/Enum/RankOption.cs
index f1725af5..53004d27 100644
--- a/src/Pixeval.CoreApi/Global/Enum/RankOption.cs
+++ b/src/Pixeval.CoreApi/Global/Enum/RankOption.cs
@@ -90,27 +90,3 @@ public enum RankOption
[Description("day_r18_ai")]
DayR18Ai
}
-
-public static class RankOptionExtension
-{
- public static readonly RankOption[] NovelRankOptions =
- [
- RankOption.Day,
- RankOption.Week,
- // RankOption.Month,
- RankOption.DayMale,
- RankOption.DayFemale,
- // RankOption.DayManga,
- // RankOption.WeekManga,
- // RankOption.MonthManga,
- RankOption.WeekOriginal,
- RankOption.WeekRookie,
- RankOption.DayR18,
- RankOption.DayMaleR18,
- RankOption.DayFemaleR18,
- RankOption.WeekR18,
- RankOption.WeekR18G,
- RankOption.DayAi,
- RankOption.DayR18Ai
- ];
-}
diff --git a/src/Pixeval.CoreApi/Global/Enum/TargetFilter.cs b/src/Pixeval.CoreApi/Global/Enum/TargetFilter.cs
index dfd3cebd..bc63dc36 100644
--- a/src/Pixeval.CoreApi/Global/Enum/TargetFilter.cs
+++ b/src/Pixeval.CoreApi/Global/Enum/TargetFilter.cs
@@ -19,17 +19,16 @@
#endregion
using System.ComponentModel;
-using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
namespace Pixeval.CoreApi.Global.Enum;
+[JsonConverter(typeof(SnakeCaseLowerEnumConverter))]
public enum TargetFilter
{
[Description("for_android")]
- [EnumMember(Value = "for_android")]
ForAndroid,
[Description("for_ios")]
- [EnumMember(Value = "for_ios")]
ForIos
}
diff --git a/src/Pixeval.CoreApi/MakoClient.Extensions.cs b/src/Pixeval.CoreApi/MakoClient.Extensions.cs
index 75cabc80..1fe209ae 100644
--- a/src/Pixeval.CoreApi/MakoClient.Extensions.cs
+++ b/src/Pixeval.CoreApi/MakoClient.Extensions.cs
@@ -95,7 +95,7 @@ public partial class MakoClient
var span = contentHtml[startIndex..endIndex];
- return JsonSerializer.Deserialize(span)!;
+ return (NovelContent)JsonSerializer.Deserialize(span, typeof(NovelContent), AppJsonSerializerContext.Default)!;
});
///
diff --git a/src/Pixeval.CoreApi/MakoClient.Logging.cs b/src/Pixeval.CoreApi/MakoClient.Logging.cs
index c4da58a3..42cefe0b 100644
--- a/src/Pixeval.CoreApi/MakoClient.Logging.cs
+++ b/src/Pixeval.CoreApi/MakoClient.Logging.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
@@ -101,7 +102,7 @@ public partial class MakoClient
}
}
- private async Task RunWithLoggerAsync(Func> task)
+ private async Task RunWithLoggerAsync(Func> task)
{
try
{
@@ -149,13 +150,14 @@ public partial class MakoClient
internal void LogException(Exception e) => Logger.LogError("MakoClient Exception", e);
+ [DynamicDependency("ConstructSystemProxy", "SystemProxyInfo", "System.Net.Http")]
static MakoClient()
{
var type = typeof(HttpClient).Assembly.GetType("System.Net.Http.SystemProxyInfo");
var method = type?.GetMethod("ConstructSystemProxy");
var @delegate = method?.CreateDelegate>();
- _getCurrentSystemProxy = @delegate ?? ThrowUtils.Throw>("Unable to find proxy functions");
+ _getCurrentSystemProxy = @delegate ?? ThrowUtils.Throw>(new("Unable to find proxy functions"));
HttpClient.DefaultProxy = _getCurrentSystemProxy();
}
diff --git a/src/Pixeval.CoreApi/MakoClient.cs b/src/Pixeval.CoreApi/MakoClient.cs
index 7c64617a..941c45e0 100644
--- a/src/Pixeval.CoreApi/MakoClient.cs
+++ b/src/Pixeval.CoreApi/MakoClient.cs
@@ -64,8 +64,9 @@ public partial class MakoClient : ICancellable, IDisposable, IAsyncDisposable
makoClient.Session = (await makoClient.Provider.GetRequiredService().RefreshAsync(new RefreshSessionRequest(refreshToken)).ConfigureAwait(false)).ToSession();
return makoClient;
}
- catch
+ catch (Exception e)
{
+ logger.LogError("Login error", e);
await makoClient.DisposeAsync();
return null;
}
diff --git a/src/Pixeval.CoreApi/Model/Comment.cs b/src/Pixeval.CoreApi/Model/Comment.cs
index 1278d6ec..f45ff6c8 100644
--- a/src/Pixeval.CoreApi/Model/Comment.cs
+++ b/src/Pixeval.CoreApi/Model/Comment.cs
@@ -30,7 +30,7 @@ public partial record Comment : IIdEntry
public required long Id { get; set; }
[JsonPropertyName("comment")]
- public required string CommentContent { get; set; }
+ public required string CommentContent { get; set; } = "";
[JsonPropertyName("date")]
public required DateTimeOffset Date { get; set; }
@@ -52,7 +52,7 @@ public partial record Stamp
public required long StampId { get; set; }
[JsonPropertyName("stamp_url")]
- public required string StampUrl { get; set; }
+ public required string StampUrl { get; set; } = DefaultImageUrls.ImageNotAvailable;
}
[Factory]
@@ -62,10 +62,10 @@ public partial record CommentUser
public required long Id { get; set; }
[JsonPropertyName("name")]
- public required string Name { get; set; }
+ public required string Name { get; set; } = "";
[JsonPropertyName("account")]
- public required string Account { get; set; }
+ public required string Account { get; set; } = "";
[JsonPropertyName("profile_image_urls")]
public required ProfileImageUrls ProfileImageUrls { get; set; }
diff --git a/src/Pixeval.CoreApi/Model/NovelContent.cs b/src/Pixeval.CoreApi/Model/NovelContent.cs
index 8d456893..fb977b89 100644
--- a/src/Pixeval.CoreApi/Model/NovelContent.cs
+++ b/src/Pixeval.CoreApi/Model/NovelContent.cs
@@ -301,8 +301,8 @@ internal class SpecialDictionaryConverter : JsonConverter
return ThrowUtils.Json();
case JsonTokenType.PropertyName:
{
- var jsonConverter = (JsonConverter)options.GetConverter(typeof(T));
- list.Add(jsonConverter.Read(ref reader, typeof(T), options)!);
+ var propertyValue = (T)JsonSerializer.Deserialize(ref reader, typeof(T), AppJsonSerializerContext.Default)!;
+ list.Add(propertyValue);
break;
}
case JsonTokenType.EndObject:
diff --git a/src/Pixeval.CoreApi/Model/TokenResponse.cs b/src/Pixeval.CoreApi/Model/TokenResponse.cs
index 83a36a3a..2202713d 100644
--- a/src/Pixeval.CoreApi/Model/TokenResponse.cs
+++ b/src/Pixeval.CoreApi/Model/TokenResponse.cs
@@ -31,19 +31,19 @@ namespace Pixeval.CoreApi.Model;
public partial record TokenResponse
{
[JsonPropertyName("access_token")]
- public required string AccessToken { get; set; }
+ public required string AccessToken { get; set; } = "";
[JsonPropertyName("expires_in")]
public required long ExpiresIn { get; set; }
[JsonPropertyName("token_type")]
- public required string TokenType { get; set; }
+ public required string TokenType { get; set; } = "";
[JsonPropertyName("scope")]
- public required string Scope { get; set; }
+ public required string Scope { get; set; } = "";
[JsonPropertyName("refresh_token")]
- public required string RefreshToken { get; set; }
+ public required string RefreshToken { get; set; } = "";
[JsonPropertyName("user")]
public required TokenUser User { get; set; }
@@ -79,13 +79,13 @@ public partial record TokenUser
public required long Id { get; set; }
[JsonPropertyName("name")]
- public required string Name { get; set; }
+ public required string Name { get; set; } = "";
[JsonPropertyName("account")]
- public required string Account { get; set; }
+ public required string Account { get; set; } = "";
[JsonPropertyName("mail_address")]
- public required string MailAddress { get; set; }
+ public required string MailAddress { get; set; } = "";
[JsonPropertyName("is_premium")]
public required bool IsPremium { get; set; }
@@ -104,11 +104,11 @@ public partial record TokenUser
public partial record TokenProfileImageUrls
{
[JsonPropertyName("px_16x16")]
- public required string Px16X16 { get; set; }
+ public required string Px16X16 { get; set; } = DefaultImageUrls.NoProfile;
[JsonPropertyName("px_50x50")]
- public required string Px50X50 { get; set; }
+ public required string Px50X50 { get; set; } = DefaultImageUrls.NoProfile;
[JsonPropertyName("px_170x170")]
- public required string Px170X170 { get; set; }
+ public required string Px170X170 { get; set; } = DefaultImageUrls.NoProfile;
}
diff --git a/src/Pixeval.CoreApi/Net/EndPoints/IAppApiEndPoint.cs b/src/Pixeval.CoreApi/Net/EndPoints/IAppApiEndPoint.cs
index 897a5974..4a6bac2b 100644
--- a/src/Pixeval.CoreApi/Net/EndPoints/IAppApiEndPoint.cs
+++ b/src/Pixeval.CoreApi/Net/EndPoints/IAppApiEndPoint.cs
@@ -53,7 +53,7 @@ public interface IAppApiEndPoint
Task GetSingleNovelAsync([AliasAs("novel_id")] long id);
[HttpGet("/webview/v2/novel")]
- Task GetNovelContentAsync([AliasAs("id")] long id, [AliasAs("raw")] bool raw = false);
+ Task GetNovelContentAsync(long id, bool raw = false);
/*
[AliasAs("viewer_version")] string viewerVersion = "20221031_ai",
[AliasAs("font")] string x1 = "mincho",
@@ -68,7 +68,7 @@ public interface IAppApiEndPoint
*/
[HttpGet("/v1/user/related")]
- Task RelatedUserAsync([AliasAs("filter")] TargetFilter filter, [AliasAs("seed_user_id")] long userId);
+ Task RelatedUserAsync(TargetFilter filter, [AliasAs("seed_user_id")] long userId);
[HttpPost("/v1/user/follow/add")]
Task FollowUserAsync([FormContent] FollowUserRequest request);
@@ -77,10 +77,10 @@ public interface IAppApiEndPoint
Task RemoveFollowUserAsync([FormContent] RemoveFollowUserRequest request);
[HttpGet("/v1/trending-tags/illust")]
- Task GetTrendingTagsAsync([AliasAs("filter")] TargetFilter filter);
+ Task GetTrendingTagsAsync(TargetFilter filter);
[HttpGet("/v1/trending-tags/novel")]
- Task GetTrendingTagsForNovelAsync([AliasAs("filter")] TargetFilter filter);
+ Task GetTrendingTagsForNovelAsync(TargetFilter filter);
[HttpGet("/v1/ugoira/metadata")]
Task GetUgoiraMetadataAsync([AliasAs("illust_id")] long id);
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivBookmarkTagResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivBookmarkTagResponse.cs
index 963555c6..4a83660c 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivBookmarkTagResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivBookmarkTagResponse.cs
@@ -26,8 +26,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivBookmarkTagResponse : PixivNextUrlResponse
+public partial record PixivBookmarkTagResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("bookmark_tags")]
- public override required BookmarkTag[] Entities { get; set; } = [];
+ public /*override*/ required BookmarkTag[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivCommentResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivCommentResponse.cs
index 3d686693..fcec183a 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivCommentResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivCommentResponse.cs
@@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivCommentResponse : PixivNextUrlResponse
+public partial record PixivCommentResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("comments")]
- public override required Comment[] Entities { get; set; } = [];
+ public /*override*/ required Comment[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivIllustrationResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivIllustrationResponse.cs
index cb2318f9..9267b93a 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivIllustrationResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivIllustrationResponse.cs
@@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivIllustrationResponse : PixivNextUrlResponse
+public partial record PixivIllustrationResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("illusts")]
- public override required Illustration[] Entities { get; set; } = [];
+ public /*override*/ required Illustration[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivNextUrlResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivNextUrlResponse.cs
index 6640cd8a..eecc8ae7 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivNextUrlResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivNextUrlResponse.cs
@@ -25,10 +25,10 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
-public abstract record PixivNextUrlResponse where TEntity : class, IEntry
+public interface IPixivNextUrlResponse where TEntity : class, IEntry
{
[JsonPropertyName("next_url")]
- public required string? NextUrl { get; set; } = null;
+ string? NextUrl { get; set; }
- public abstract TEntity[] Entities { get; set; }
+ TEntity[] Entities { get; set; }
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivNovelResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivNovelResponse.cs
index faccba55..85d9ee1f 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivNovelResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivNovelResponse.cs
@@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivNovelResponse : PixivNextUrlResponse
+public partial record PixivNovelResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("novels")]
- public override required Novel[] Entities { get; set; } = [];
+ public /*override*/ required Novel[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivSpotlightResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivSpotlightResponse.cs
index 28b26755..3784b1b8 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivSpotlightResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivSpotlightResponse.cs
@@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivSpotlightResponse : PixivNextUrlResponse
+public partial record PixivSpotlightResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("spotlight_articles")]
- public override required Spotlight[] Entities { get; set; } = [];
+ public /*override*/ required Spotlight[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Net/Response/PixivUserResponse.cs b/src/Pixeval.CoreApi/Net/Response/PixivUserResponse.cs
index 2c1498a0..11dd799a 100644
--- a/src/Pixeval.CoreApi/Net/Response/PixivUserResponse.cs
+++ b/src/Pixeval.CoreApi/Net/Response/PixivUserResponse.cs
@@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
-public partial record PixivUserResponse : PixivNextUrlResponse
+public partial record PixivUserResponse : IPixivNextUrlResponse
{
+ [JsonPropertyName("next_url")]
+ public required string? NextUrl { get; set; }
+
[JsonPropertyName("user_previews")]
- public override required User[] Entities { get; set; } = [];
+ public /*override*/ required User[] Entities { get; set; } = [];
}
diff --git a/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj b/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj
index 8e074075..d2e0456c 100644
--- a/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj
+++ b/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj
@@ -6,13 +6,13 @@
true
enable
preview
+ true
-
diff --git a/src/Pixeval.CoreApi/Preference/Session.cs b/src/Pixeval.CoreApi/Preference/Session.cs
index ff82d481..6b5a88f6 100644
--- a/src/Pixeval.CoreApi/Preference/Session.cs
+++ b/src/Pixeval.CoreApi/Preference/Session.cs
@@ -19,8 +19,8 @@
#endregion
using System;
+using System.Text.Json;
using System.Text.Json.Serialization;
-using Pixeval.Utilities;
namespace Pixeval.CoreApi.Preference;
@@ -46,5 +46,5 @@ public record Session(
[property: JsonPropertyName("account")] string Account,
[property: JsonPropertyName("isPremium")] bool IsPremium)
{
- public override string? ToString() => this.ToJson();
+ public override string ToString() => JsonSerializer.Serialize(this, typeof(Session), AppJsonSerializerContext.Default);
}
diff --git a/src/Pixeval.SourceGen/FactoryGenerator.cs b/src/Pixeval.SourceGen/FactoryGenerator.cs
index 5953a872..b744ba69 100644
--- a/src/Pixeval.SourceGen/FactoryGenerator.cs
+++ b/src/Pixeval.SourceGen/FactoryGenerator.cs
@@ -32,8 +32,12 @@ public class FactoryGenerator : IIncrementalGenerator
IdentifierName(symbol.Name),
syntax.Initializer is { } init
? init.Value
- : symbol.Type.NullableAnnotation is NullableAnnotation.NotAnnotated && symbol.Type.GetAttributes().Any(i => i.AttributeClass?.MetadataName == AttributeName)
- ? InvocationExpression(symbol.Type.GetStaticMemberAccessExpression(createDefault))
+ : symbol.Type.NullableAnnotation is NullableAnnotation.NotAnnotated && symbol.Type
+ .GetAttributes().Any(i => i.AttributeClass?.MetadataName == AttributeName)
+ ? InvocationExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(symbol.Type.ToDisplayString()),
+ IdentifierName(createDefault)))
: DefaultExpression(symbol.Type.GetTypeSyntax(false)));
});
@@ -70,12 +74,10 @@ public class FactoryGenerator : IIncrementalGenerator
AttributeFullName,
(_, _) => true,
(syntaxContext, _) => syntaxContext
- ).Combine(context.CompilationProvider);
+ );
- context.RegisterSourceOutput(generatorAttributes, (spc, tuple) =>
+ context.RegisterSourceOutput(generatorAttributes, (spc, ga) =>
{
- var (ga, compilation) = tuple;
-
if (ga.TargetSymbol is not INamedTypeSymbol symbol)
return;
diff --git a/src/Pixeval.SourceGen/LocalizationMetadataGenerator.cs b/src/Pixeval.SourceGen/LocalizationMetadataGenerator.cs
new file mode 100644
index 00000000..f2af42c4
--- /dev/null
+++ b/src/Pixeval.SourceGen/LocalizationMetadataGenerator.cs
@@ -0,0 +1,313 @@
+#region Copyright
+// GPL v3 License
+//
+// Pixeval/Pixeval.SourceGen
+// Copyright (c) 2024 Pixeval.SourceGen/EnumLocalizationMetadataGenerator.cs
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+#endregion
+
+using Microsoft.CodeAnalysis;
+using static Pixeval.SourceGen.SyntaxHelper;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using System.Collections.Generic;
+
+namespace Pixeval.SourceGen;
+
+public abstract class LocalizationMetadataGeneratorBase
+{
+ public static ClassDeclarationSyntax GetClassDeclaration(string typeFullName, string name, string resourceTypeName, List<(string Name, string ResourceName)> members, bool isPartial)
+ {
+ const string stringRepresentableItemName = "global::Pixeval.Controls.StringRepresentableItem";
+ const string valuesTable = "ValuesTable";
+ const string getResource = "GetResource";
+ return ClassDeclaration(name)
+ .AddModifiers(Token(SyntaxKind.StaticKeyword),
+ isPartial ? Token(SyntaxKind.PartialKeyword) : Token(SyntaxKind.PublicKeyword))
+ .WithOpenBraceToken(Token(SyntaxKind.OpenBraceToken))
+ .AddMembers(
+ MethodDeclaration(
+ ParseTypeName(
+ $"global::System.Collections.Generic.IReadOnlyList<{stringRepresentableItemName}>"),
+ "GetItems")
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithBody(
+ Block(List([
+ LocalDeclarationStatement(
+ VariableDeclaration(ParseTypeName("var"), SeparatedList([
+ VariableDeclarator("array").WithInitializer(EqualsValueClause(
+ ArrayCreationExpression(
+ ArrayType(
+ ParseTypeName(stringRepresentableItemName),
+ List([
+ ArrayRankSpecifier(SeparatedList([
+ (ExpressionSyntax)MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(valuesTable),
+ IdentifierName("Count"))
+ ]))
+ ])))))
+ ]))),
+ LocalDeclarationStatement(
+ VariableDeclaration(ParseTypeName("var"), SeparatedList([
+ VariableDeclarator("i").WithInitializer(EqualsValueClause(
+ LiteralExpression(SyntaxKind.NumericLiteralExpression, Literal(0))))
+ ]))),
+ ForEachStatement(ParseTypeName("var"), Identifier("t"), IdentifierName(valuesTable),
+ Block(List([
+ ExpressionStatement(
+ AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
+ ElementAccessExpression(IdentifierName("array"),
+ BracketedArgumentList(SeparatedList([Argument(IdentifierName("i"))]))),
+ ObjectCreationExpression(ParseTypeName(stringRepresentableItemName),
+ ArgumentList(SeparatedList([
+ Argument(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("t"),
+ IdentifierName("Key"))),
+ Argument(InvocationExpression(
+ MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(resourceTypeName),
+ IdentifierName(getResource)),
+ ArgumentList(SeparatedList([
+ Argument(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("t"),
+ IdentifierName("Value")))
+ ]))))
+ ])), null))),
+ ExpressionStatement(PrefixUnaryExpression(SyntaxKind.PreIncrementExpression,
+ IdentifierName("i")))
+ ]))),
+ ReturnStatement(IdentifierName("array"))
+ ]))),
+ MethodDeclaration(ParseTypeName("string"), getResource)
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithParameterList(ParameterList(SeparatedList([
+ Parameter(Identifier("id")).WithType(ParseTypeName("string"))
+ ])))
+ .WithExpressionBody(
+ ArrowExpressionClause(
+ InvocationExpression(
+ MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(resourceTypeName), IdentifierName(getResource)),
+ ArgumentList(SeparatedList([
+ Argument(IdentifierName("id"))
+ ])))))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
+ MethodDeclaration(ParseTypeName("string"), getResource)
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithParameterList(ParameterList(SeparatedList([
+ Parameter(Identifier("value")).WithType(ParseTypeName(typeFullName))
+ ])))
+ .WithExpressionBody(
+ ArrowExpressionClause(
+ InvocationExpression(
+ MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(resourceTypeName), IdentifierName(getResource)),
+ ArgumentList(SeparatedList([
+ Argument(ElementAccessExpression(
+ IdentifierName(valuesTable), BracketedArgumentList(SeparatedList([
+ Argument(IdentifierName("value"))
+ ]))))
+ ])))))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
+ PropertyDeclaration(
+ ParseTypeName("global::System.Collections.Frozen.FrozenDictionary"),
+ "NamesTable")
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithExpressionBody(ArrowExpressionClause(InvocationExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName("global::System.Collections.Frozen.FrozenDictionary"),
+ IdentifierName("ToFrozenDictionary")),
+ ArgumentList(SeparatedList([
+ Argument(
+ ObjectCreationExpression(
+ ParseTypeName("global::System.Collections.Generic.Dictionary"),
+ null,
+ InitializerExpression(SyntaxKind.ObjectInitializerExpression,
+ SeparatedList(members.Select(member =>
+ (ExpressionSyntax)AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ ImplicitElementAccess(BracketedArgumentList(SeparatedList(
+ [Argument(NameOfExpression(member.Name))]
+ ))),
+ NameOfExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(resourceTypeName),
+ IdentifierName(member.ResourceName)))))))))
+ ])))))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
+ PropertyDeclaration(
+ ParseTypeName($"{FrozenDictionaryTypeName}<{typeFullName}, string>"),
+ valuesTable)
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithExpressionBody(ArrowExpressionClause(InvocationExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(FrozenDictionaryTypeName),
+ IdentifierName("ToFrozenDictionary")),
+ ArgumentList(SeparatedList([
+ Argument(
+ ObjectCreationExpression(
+ ParseTypeName(
+ $"global::System.Collections.Generic.Dictionary<{typeFullName}, string>"),
+ null,
+ InitializerExpression(SyntaxKind.ObjectInitializerExpression,
+ SeparatedList(members.Select(member =>
+ (ExpressionSyntax)AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ ImplicitElementAccess(BracketedArgumentList(SeparatedList(
+ [Argument(IdentifierName(member.Name))]
+ ))),
+ NameOfExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(resourceTypeName),
+ IdentifierName(member.ResourceName)))))))))
+ ])))))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)))
+ .WithCloseBraceToken(Token(SyntaxKind.CloseBraceToken));
+ }
+}
+
+[Generator]
+public class LocalizationMetadataGenerator : LocalizationMetadataGeneratorBase, IIncrementalGenerator
+{
+ private const string AttributeName = "LocalizationMetadataAttribute";
+
+ private const string SubAttributeName = "LocalizedResourceAttribute";
+
+ private const string AttributeNamespace = nameof(Pixeval) + ".Attributes";
+
+ private const string AttributeFullName = AttributeNamespace + "." + AttributeName;
+
+ private const string SubAttributeFullName = AttributeNamespace + "." + SubAttributeName;
+
+ internal string? TypeWithAttribute(INamedTypeSymbol typeSymbol, ImmutableArray attributeList)
+ {
+ if (attributeList is not [{ ConstructorArguments: [{ Value: INamedTypeSymbol resourceType }, ..] }])
+ return null;
+
+ var isPartial = attributeList is [{ NamedArguments: [{ Key: "IsPartial", Value.Value: true }] }];
+
+ List<(string Name, string ResourceName)> members = [];
+
+ foreach (var member in typeSymbol.GetMembers())
+ {
+ if (member is not IFieldSymbol field)
+ continue;
+
+ if (field.GetAttribute(SubAttributeFullName) is not { ConstructorArguments: [_, { Value: string resourceName }, ..] })
+ continue;
+
+ members.Add((typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." + field.Name, resourceName));
+ }
+
+ var generatedType = GetClassDeclaration(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), $"{typeSymbol.Name}Extension", resourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), members, isPartial);
+
+ var generatedNamespace = GetFileScopedNamespaceDeclaration(typeSymbol.ContainingNamespace.ToDisplayString(), generatedType, true);
+ var compilationUnit = CompilationUnit()
+ .AddMembers(generatedNamespace)
+ .NormalizeWhitespace();
+ return SyntaxTree(compilationUnit, encoding: Encoding.UTF8).GetText().ToString();
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var generatorAttributes = context.SyntaxProvider.ForAttributeWithMetadataName(
+ AttributeFullName,
+ (_, _) => true,
+ (syntaxContext, _) => syntaxContext
+ );
+
+ context.RegisterSourceOutput(generatorAttributes, (spc, ga) =>
+ {
+ if (ga.TargetSymbol is not INamedTypeSymbol symbol)
+ return;
+
+ if (TypeWithAttribute(symbol, ga.Attributes) is { } source)
+ spc.AddSource(
+ // 不能重名
+ $"{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}_{AttributeFullName}.g.cs",
+ source);
+ });
+ }
+}
+
+[Generator]
+public class AttachedLocalizationMetadataGenerator : LocalizationMetadataGeneratorBase, IIncrementalGenerator
+{
+ private const string AttributeName = "AttachedLocalizationMetadataAttribute`1";
+
+ private const string SubAttributeName = "AttachedLocalizedResourceAttribute";
+
+ private const string AttributeNamespace = nameof(Pixeval) + ".Attributes";
+
+ private const string AttributeFullName = AttributeNamespace + "." + AttributeName;
+
+ private const string SubAttributeFullName = AttributeNamespace + "." + SubAttributeName;
+
+ internal string? TypeWithAttribute(INamedTypeSymbol typeSymbol, ImmutableArray attributeList)
+ {
+ if (attributeList is not [{ ConstructorArguments: [{ Value: INamedTypeSymbol resourceType }, ..], AttributeClass.TypeArguments: [INamedTypeSymbol attachedType, ..] }])
+ return null;
+
+ List<(string Name, string ResourceName)> members = [];
+
+ foreach (var attribute in typeSymbol.GetAttributes())
+ {
+ if (attribute.AttributeClass?.ToDisplayString() is not SubAttributeFullName)
+ continue;
+
+ if (attribute is not { ConstructorArguments: [{ Value: string fieldName }, { Value: string resourceName }, ..] })
+ continue;
+
+ members.Add((attachedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) + "." + fieldName, resourceName));
+ }
+
+ var generatedType = GetClassDeclaration(attachedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), typeSymbol.Name, resourceType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), members, true);
+
+ var generatedNamespace = GetFileScopedNamespaceDeclaration(typeSymbol.ContainingNamespace.ToDisplayString(), generatedType, true);
+ var compilationUnit = CompilationUnit()
+ .AddMembers(generatedNamespace)
+ .NormalizeWhitespace();
+ return SyntaxTree(compilationUnit, encoding: Encoding.UTF8).GetText().ToString();
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var generatorAttributes = context.SyntaxProvider.ForAttributeWithMetadataName(
+ AttributeFullName,
+ (_, _) => true,
+ (syntaxContext, _) => syntaxContext
+ );
+
+ context.RegisterSourceOutput(generatorAttributes, (spc, ga) =>
+ {
+ if (ga.TargetSymbol is not INamedTypeSymbol symbol)
+ return;
+
+ if (TypeWithAttribute(symbol, ga.Attributes) is { } source)
+ spc.AddSource(
+ // 不能重名
+ $"{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted))}_{AttributeFullName}.g.cs",
+ source);
+ });
+ }
+}
diff --git a/src/Pixeval.SourceGen/MetaPathMacroGenerator.cs b/src/Pixeval.SourceGen/MetaPathMacroGenerator.cs
new file mode 100644
index 00000000..dab0bd25
--- /dev/null
+++ b/src/Pixeval.SourceGen/MetaPathMacroGenerator.cs
@@ -0,0 +1,97 @@
+#region Copyright
+// GPL v3 License
+//
+// Pixeval/Pixeval.SourceGen
+// Copyright (c) 2024 Pixeval.SourceGen/MetaPathMacroGenerator.cs
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+#endregion
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.CSharp;
+using static Pixeval.SourceGen.SyntaxHelper;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Pixeval.SourceGen;
+
+[Generator]
+public class MetaPathMacroGenerator : IIncrementalGenerator
+{
+ private const string AttributeName = "MetaPathMacroAttribute`1";
+
+ private const string AttributeNamespace = nameof(Pixeval) + ".Download.Macros";
+
+ private const string AttributeFullName = AttributeNamespace + "." + AttributeName;
+
+ internal string TypeWithAttribute(ImmutableArray gas, out string fileName)
+ {
+ var dictionary = new Dictionary>(SymbolEqualityComparer.Default);
+ foreach (var ga in gas)
+ {
+ if (ga is not { TargetSymbol: INamedTypeSymbol symbol, Attributes: var attributeList })
+ continue;
+ foreach (var attributeData in attributeList)
+ if (attributeData.AttributeClass?.TypeArguments is [{ } type])
+ if (dictionary.TryGetValue(type, out var list))
+ list.Add(symbol);
+ else
+ dictionary[type] = [symbol];
+ }
+
+ const string getAttachedTypeInstances = "Get{0}Instances";
+
+ fileName = "MetaPathMacroAttributeHelper.g.cs";
+ var generatedType = ClassDeclaration("MetaPathMacroAttributeHelper")
+ .AddModifiers(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword))
+ .WithOpenBraceToken(Token(SyntaxKind.OpenBraceToken))
+ .AddMembers(dictionary.Select(t =>
+ (MemberDeclarationSyntax)MethodDeclaration(
+ ParseTypeName("global::System.Collections.Generic.IReadOnlyList"),
+ string.Format(getAttachedTypeInstances, t.Key.Name))
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithExpressionBody(ArrowExpressionClause(CollectionExpression(SeparatedList(t.Value.Select(v =>
+ (CollectionElementSyntax)ExpressionElement(ObjectCreationExpression(v.GetTypeSyntax(false))
+ .WithArgumentList(ArgumentList()))))
+ )))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))).ToArray()
+ )
+ .WithCloseBraceToken(Token(SyntaxKind.CloseBraceToken));
+ var generatedNamespace = GetFileScopedNamespaceDeclaration(AttributeNamespace, generatedType, true);
+ var compilationUnit = CompilationUnit()
+ .AddMembers(generatedNamespace)
+ .NormalizeWhitespace();
+ return SyntaxTree(compilationUnit, encoding: Encoding.UTF8).GetText().ToString();
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var generatorAttributes = context.SyntaxProvider.ForAttributeWithMetadataName(
+ AttributeFullName,
+ (_, _) => true,
+ (syntaxContext, _) => syntaxContext
+ ).Collect();
+
+ context.RegisterSourceOutput(generatorAttributes, (spc, gas) =>
+ {
+ if (gas.Length > 0)
+ if (TypeWithAttribute(gas, out var name) is { } source)
+ spc.AddSource(name, source);
+ });
+ }
+}
diff --git a/src/Pixeval.SourceGen/Pixeval.SourceGen.csproj b/src/Pixeval.SourceGen/Pixeval.SourceGen.csproj
index 72a8ba69..baab2964 100644
--- a/src/Pixeval.SourceGen/Pixeval.SourceGen.csproj
+++ b/src/Pixeval.SourceGen/Pixeval.SourceGen.csproj
@@ -6,16 +6,17 @@
true
Generated
true
- AnyCPU
- Debug;Release
+ Debug;Release
+ AnyCPU
latest
+ false
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
+
\ No newline at end of file
diff --git a/src/Pixeval.SourceGen/SettingsEntryGenerator.cs b/src/Pixeval.SourceGen/SettingsEntryGenerator.cs
new file mode 100644
index 00000000..8f6f982d
--- /dev/null
+++ b/src/Pixeval.SourceGen/SettingsEntryGenerator.cs
@@ -0,0 +1,123 @@
+#region Copyright
+
+// GPL v3 License
+//
+// Pixeval/Pixeval.SourceGen
+// Copyright (c) 2024 Pixeval.SourceGen/SettingsEntryGenerator.cs
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#endregion
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using static Pixeval.SourceGen.SyntaxHelper;
+using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
+
+namespace Pixeval.SourceGen;
+
+// [Generator]
+public class SettingsEntryGenerator : IIncrementalGenerator
+{
+ private const string AttributeName = "SettingsEntryAttribute";
+
+ private const string AttributeNamespace = nameof(Pixeval) + ".Attributes";
+
+ private const string AttributeFullName = AttributeNamespace + "." + AttributeName;
+
+ internal string TypeWithAttribute(ImmutableArray gas, out string fileName)
+ {
+ var list = new List<(IPropertySymbol Property, (int, string, string) Attribute)>();
+ foreach (var ga in gas)
+ if (ga is
+ {
+ TargetSymbol: IPropertySymbol symbol,
+ Attributes: [{ ConstructorArguments: [{ Value: int a }, { Value: string b }, { Value: string c }] }]
+ })
+ list.Add((symbol, (a, b, c)));
+
+ const string metadataTable = "MetadataTable";
+
+ fileName = "SettingsEntryAttribute.g.cs";
+ var generatedType = ClassDeclaration("SettingsEntryAttribute")
+ .AddModifiers(Token(SyntaxKind.PartialKeyword))
+ .WithOpenBraceToken(Token(SyntaxKind.OpenBraceToken))
+ .AddMembers(
+ PropertyDeclaration(
+ ParseTypeName($"{FrozenDictionaryTypeName}"),
+ metadataTable)
+ .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword)))
+ .WithExpressionBody(ArrowExpressionClause(InvocationExpression(MemberAccessExpression(
+ SyntaxKind.SimpleMemberAccessExpression,
+ IdentifierName(FrozenDictionaryTypeName),
+ IdentifierName("ToFrozenDictionary")),
+ ArgumentList(SeparatedList([
+ Argument(
+ ObjectCreationExpression(
+ ParseTypeName(
+ $"global::System.Collections.Generic.Dictionary"),
+ null,
+ InitializerExpression(SyntaxKind.ObjectInitializerExpression,
+ SeparatedList(list.Select(elem =>
+ (ExpressionSyntax)AssignmentExpression(
+ SyntaxKind.SimpleAssignmentExpression,
+ ImplicitElementAccess(BracketedArgumentList(SeparatedList(
+ [
+ Argument(NameOfExpression(
+ elem.Property.ContainingType.ToDisplayString(
+ SymbolDisplayFormat.FullyQualifiedFormat) + "." +
+ elem.Property.Name))
+ ]
+ ))),
+ ObjectCreationExpression(ParseTypeName("global::" + AttributeFullName))
+ .WithArgumentList(ArgumentList(SeparatedList([
+ Argument(LiteralExpression(SyntaxKind.NumericLiteralExpression,
+ Literal(elem.Attribute.Item1))),
+ Argument(LiteralExpression(SyntaxKind.StringLiteralExpression,
+ Literal(elem.Attribute.Item2))),
+ Argument(LiteralExpression(SyntaxKind.StringLiteralExpression,
+ Literal(elem.Attribute.Item3)))
+ ])))))))))
+ ])))))
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
+ )
+ .WithCloseBraceToken(Token(SyntaxKind.CloseBraceToken));
+ var generatedNamespace = GetFileScopedNamespaceDeclaration(AttributeNamespace, generatedType, true);
+ var compilationUnit = CompilationUnit()
+ .AddMembers(generatedNamespace)
+ .NormalizeWhitespace();
+ return SyntaxTree(compilationUnit, encoding: Encoding.UTF8).GetText().ToString();
+ }
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var generatorAttributes = context.SyntaxProvider.ForAttributeWithMetadataName(
+ AttributeFullName,
+ (_, _) => true,
+ (syntaxContext, _) => syntaxContext
+ ).Collect();
+
+ context.RegisterSourceOutput(generatorAttributes, (spc, gas) =>
+ {
+ if (gas.Length > 0)
+ if (TypeWithAttribute(gas, out var name) is { } source)
+ spc.AddSource(name, source);
+ });
+ }
+}
diff --git a/src/Pixeval.SourceGen/SyntaxHelper.cs b/src/Pixeval.SourceGen/SyntaxHelper.cs
index 7ecf37f7..17ae2481 100644
--- a/src/Pixeval.SourceGen/SyntaxHelper.cs
+++ b/src/Pixeval.SourceGen/SyntaxHelper.cs
@@ -32,6 +32,7 @@ public static class SyntaxHelper
{
internal const string AttributeNamespace = "WinUI3Utilities.Attributes.";
internal const string DisableSourceGeneratorAttribute = AttributeNamespace + "DisableSourceGeneratorAttribute";
+ internal const string FrozenDictionaryTypeName = "global::System.Collections.Frozen.FrozenDictionary";
public static bool HasAttribute(this ISymbol s, string attributeFqName)
{
@@ -43,19 +44,6 @@ public static class SyntaxHelper
return mds.GetAttributes().FirstOrDefault(attr => attr?.AttributeClass?.ToDisplayString() == attributeFqName);
}
- ///
- /// 缩进(伪tab)
- ///
- /// tab数量
- /// 4n个space
- internal static string Spacing(int n)
- {
- var temp = "";
- for (var i = 0; i < n; ++i)
- temp += " ";
- return temp;
- }
-
///
/// Generate the following code
///
@@ -69,13 +57,23 @@ public static class SyntaxHelper
return isNullable ? NullableType(typeName) : typeName;
}
- internal static MemberAccessExpressionSyntax GetStaticMemberAccessExpression(this ITypeSymbol typeSymbol, string name)
- {
- return MemberAccessExpression(
- SyntaxKind.SimpleMemberAccessExpression,
- IdentifierName(typeSymbol.ToDisplayString()),
- IdentifierName(name));
- }
+ ///
+ /// Generate the following code
+ ///
+ /// nameof()
+ ///
+ ///
+ /// NameOfExpression
+ internal static InvocationExpressionSyntax NameOfExpression(string name) => NameOfExpression(IdentifierName(name));
+
+ ///
+ /// Generate the following code
+ ///
+ /// nameof()
+ ///
+ ///
+ /// NameOfExpression
+ internal static InvocationExpressionSyntax NameOfExpression(ExpressionSyntax expressionSyntax) => InvocationExpression(IdentifierName("nameof"), ArgumentList().AddArguments(Argument(expressionSyntax)));
internal static IEnumerable GetProperties(this ITypeSymbol typeSymbol, INamedTypeSymbol attribute)
{
@@ -114,31 +112,6 @@ public static class SyntaxHelper
return false;
}
- ///
- /// 获取某的namespace并加入集合
- ///
- /// namespaces集合
- /// 已判断过的types
- /// 上下文所在的类
- /// type的symbol
- internal static void UseNamespace(this HashSet namespaces, HashSet usedTypes, INamedTypeSymbol contextType, ITypeSymbol symbol)
- {
- if (usedTypes.Contains(symbol))
- return;
-
- _ = usedTypes.Add(symbol);
-
- if (symbol.ContainingNamespace is not { } ns)
- return;
-
- if (!SymbolEqualityComparer.Default.Equals(ns, contextType.ContainingNamespace))
- _ = namespaces.Add(ns.ToDisplayString());
-
- if (symbol is INamedTypeSymbol { IsGenericType: true } genericSymbol)
- foreach (var a in genericSymbol.TypeArguments)
- namespaces.UseNamespace(usedTypes, contextType, a);
- }
-
///
/// Generate the following code
///
@@ -163,21 +136,21 @@ public static class SyntaxHelper
///
/// Generate the following code
///
- /// partial
+ /// partial
/// {
///
/// }
///
///
/// TypeDeclaration
- internal static TypeDeclarationSyntax GetDeclaration(string name, INamedTypeSymbol symbol, params MemberDeclarationSyntax[] member)
+ internal static TypeDeclarationSyntax GetDeclaration(string name, INamedTypeSymbol originalSymbol, params MemberDeclarationSyntax[] member)
{
- TypeDeclarationSyntax typeDeclarationTemp = symbol.TypeKind switch
+ TypeDeclarationSyntax typeDeclarationTemp = originalSymbol.TypeKind switch
{
- TypeKind.Class when !symbol.IsRecord => ClassDeclaration(name),
- TypeKind.Struct when !symbol.IsRecord => StructDeclaration(name),
- TypeKind.Class or TypeKind.Struct when symbol.IsRecord => RecordDeclaration(Token(SyntaxKind.RecordKeyword), name),
- _ => throw new ArgumentOutOfRangeException(nameof(symbol.TypeKind))
+ TypeKind.Class when !originalSymbol.IsRecord => ClassDeclaration(name),
+ TypeKind.Struct when !originalSymbol.IsRecord => StructDeclaration(name),
+ TypeKind.Class or TypeKind.Struct when originalSymbol.IsRecord => RecordDeclaration(Token(SyntaxKind.RecordKeyword), name),
+ _ => throw new ArgumentOutOfRangeException(nameof(originalSymbol.TypeKind))
};
return typeDeclarationTemp.AddModifiers(Token(SyntaxKind.PartialKeyword))
.WithOpenBraceToken(Token(SyntaxKind.OpenBraceToken))
diff --git a/src/Pixeval.Utilities/AsyncLazy.cs b/src/Pixeval.Utilities/AsyncLazy.cs
index 27090147..d3672b8d 100644
--- a/src/Pixeval.Utilities/AsyncLazy.cs
+++ b/src/Pixeval.Utilities/AsyncLazy.cs
@@ -20,6 +20,7 @@
using System;
using System.Runtime.CompilerServices;
+using System.Threading;
using System.Threading.Tasks;
namespace Pixeval.Utilities;
@@ -37,6 +38,8 @@ public class AsyncLazy
///
private T? _value;
+ private readonly SemaphoreSlim _slimLock = new(1, 1);
+
public AsyncLazy(T value)
{
_value = value;
@@ -52,7 +55,18 @@ public class AsyncLazy
public ValueTask ValueAsync => ValueAsyncInternal();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private async ValueTask ValueAsyncInternal() => IsValueCreated ? _value! : await CreateValueAsync();
+ private async ValueTask ValueAsyncInternal()
+ {
+ await _slimLock.WaitAsync();
+ try
+ {
+ return IsValueCreated ? _value! : await CreateValueAsync();
+ }
+ finally
+ {
+ _ = _slimLock.Release();
+ }
+ }
private async Task CreateValueAsync()
{
diff --git a/src/Pixeval.Utilities/Description.cs b/src/Pixeval.Utilities/Description.cs
index 009a02d5..61fbc0f1 100644
--- a/src/Pixeval.Utilities/Description.cs
+++ b/src/Pixeval.Utilities/Description.cs
@@ -20,13 +20,14 @@
using System;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace Pixeval.Utilities;
public static class DescriptionHelper
{
- public static string GetDescription(this TEnum @enum) where TEnum : Enum
+ public static string GetDescription<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>(this TEnum @enum) where TEnum : Enum
{
return (typeof(TEnum).GetField(@enum.ToString())?.GetCustomAttribute(typeof(DescriptionAttribute)) as DescriptionAttribute)?.Description ?? ThrowUtils.InvalidOperation("Attribute not found");
}
diff --git a/src/Pixeval.Utilities/Objects.cs b/src/Pixeval.Utilities/Objects.cs
index e6d94493..9f048288 100644
--- a/src/Pixeval.Utilities/Objects.cs
+++ b/src/Pixeval.Utilities/Objects.cs
@@ -19,7 +19,6 @@
#endregion
using System;
-using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -28,10 +27,8 @@ using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
-using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
-using CommunityToolkit.HighPerformance;
using CommunityToolkit.HighPerformance.Buffers;
namespace Pixeval.Utilities;
@@ -118,39 +115,6 @@ public static class Objects
return bytes.Select(b => b.ToString("x2")).Aggregate(string.Concat);
}
- public static async Task ToJsonAsync(this TEntity? obj, Action? serializerOptionConfigure = null)
- {
- if (obj is null)
- {
- return null;
- }
-
- await using var memoryStream = new MemoryStream();
- await JsonSerializer.SerializeAsync(memoryStream, obj, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option))).ConfigureAwait(false);
- return memoryStream.ToArray().GetString();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string? ToJson(this object? obj, Action? serializerOptionConfigure = null)
- {
- return obj?.Let(o => JsonSerializer.Serialize(o, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option))));
- }
-
- public static async ValueTask FromJsonAsync(this IMemoryOwner bytes, Action? serializerOptionConfigure = null)
- {
- using (bytes)
- {
- await using var stream = bytes.Memory.AsStream();
- return await JsonSerializer.DeserializeAsync(stream, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option))).ConfigureAwait(false);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TEntity? FromJson(this string str, Action? serializerOptionConfigure = null)
- {
- return JsonSerializer.Deserialize(str, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option)));
- }
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsIgnoreCase(this string str1, string str2)
{
diff --git a/src/Pixeval.Utilities/Pixeval.Utilities.csproj b/src/Pixeval.Utilities/Pixeval.Utilities.csproj
index 3c61edfd..9c646280 100644
--- a/src/Pixeval.Utilities/Pixeval.Utilities.csproj
+++ b/src/Pixeval.Utilities/Pixeval.Utilities.csproj
@@ -5,6 +5,7 @@
x86;x64;arm64
enable
preview
+ true
diff --git a/src/Pixeval.Utilities/ThrowUtils.cs b/src/Pixeval.Utilities/ThrowUtils.cs
index 1f4a3b15..0578ec58 100644
--- a/src/Pixeval.Utilities/ThrowUtils.cs
+++ b/src/Pixeval.Utilities/ThrowUtils.cs
@@ -39,8 +39,8 @@ public static class ThrowUtils
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
- public static TResult Throw(params object[] parameters) where TException : Exception
- => throw ((Exception)Activator.CreateInstance(typeof(TException), parameters)!);
+ public static TResult Throw(TException e) where TException : Exception
+ => throw e;
///
[DoesNotReturn]
diff --git a/src/Pixeval/App.xaml.cs b/src/Pixeval/App.xaml.cs
index 200c3b37..b33304e4 100644
--- a/src/Pixeval/App.xaml.cs
+++ b/src/Pixeval/App.xaml.cs
@@ -55,6 +55,7 @@ public partial class App
public App()
{
_ = this.UseSegoeMetrics();
+ SettingsValueConverter.Context = SettingsSerializeContext.Default;
AppViewModel = new AppViewModel(this);
BookmarkTag.AllCountedTagString = MiscResources.AllCountedTagName;
AppInfo.SetNameResolvers(AppViewModel.AppSettings);
diff --git a/src/Pixeval/AppManagement/AppSettings.cs b/src/Pixeval/AppManagement/AppSettings.cs
index 9cf26580..d3240e43 100644
--- a/src/Pixeval/AppManagement/AppSettings.cs
+++ b/src/Pixeval/AppManagement/AppSettings.cs
@@ -22,6 +22,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Text.Json.Serialization;
using Windows.Foundation;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
@@ -301,3 +302,7 @@ public partial record AppSettings() : IWindowSettings
: $"@{{if_illust={picPath}}}@{{if_novel={docPath}}}";
}
}
+
+[JsonSerializable(typeof(string[]))]
+[JsonSerializable(typeof(HashSet))]
+public partial class SettingsSerializeContext : JsonSerializerContext;
diff --git a/src/Pixeval/AppManagement/Versioning.cs b/src/Pixeval/AppManagement/Versioning.cs
index 5638a087..c0a1f987 100644
--- a/src/Pixeval/AppManagement/Versioning.cs
+++ b/src/Pixeval/AppManagement/Versioning.cs
@@ -49,7 +49,7 @@ public class Versioning
AppReleaseModels = null;
if (client.DefaultRequestHeaders.UserAgent.Count is 0)
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0");
- if (await client.GetFromJsonAsync("https://api.github.com/repos/Pixeval/Pixeval/releases") is { Length: > 0 } gitHubReleases)
+ if (await client.GetFromJsonAsync("https://api.github.com/repos/Pixeval/Pixeval/releases", typeof(GitHubRelease[]), GitHubReleaseSerializeContext.Default) is GitHubRelease[] { Length: > 0 } gitHubReleases)
{
var appReleaseModels = new List(gitHubReleases.Length);
foreach (var release in gitHubReleases)
@@ -102,7 +102,11 @@ public record AppReleaseModel(
}
}
-file class GitHubRelease
+[JsonSerializable(typeof(GitHubRelease[]))]
+[JsonSerializable(typeof(Assets[]))]
+public partial class GitHubReleaseSerializeContext : JsonSerializerContext;
+
+public class GitHubRelease
{
[JsonPropertyName("tag_name")]
public required string TagName { get; init; }
@@ -114,7 +118,7 @@ file class GitHubRelease
public required string Notes { get; init; }
}
-file class Assets
+public class Assets
{
[JsonPropertyName("browser_download_url")]
public required string BrowserDownloadUrl { get; init; }
diff --git a/src/Pixeval/AppViewModel.cs b/src/Pixeval/AppViewModel.cs
index 7f8153dc..61de4cde 100644
--- a/src/Pixeval/AppViewModel.cs
+++ b/src/Pixeval/AppViewModel.cs
@@ -37,7 +37,7 @@ using Windows.Storage;
namespace Pixeval;
-public class AppViewModel(App app) : IDisposable
+public partial class AppViewModel(App app) : IDisposable
{
private bool _activatedByProtocol;
diff --git a/src/Pixeval/Attributes/SettingsEntryAttribute.cs b/src/Pixeval/Attributes/SettingsEntryAttribute.cs
index 65c5a4b7..e0b03c78 100644
--- a/src/Pixeval/Attributes/SettingsEntryAttribute.cs
+++ b/src/Pixeval/Attributes/SettingsEntryAttribute.cs
@@ -20,18 +20,23 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using FluentIcons.Common;
using Pixeval.AppManagement;
-using Pixeval.Util;
using Pixeval.Utilities;
using WinUI3Utilities;
namespace Pixeval.Attributes;
[AttributeUsage(AttributeTargets.Property)]
-public class SettingsEntryAttribute(Symbol symbol, string resourceKeyHeader, string? resourceKeyDescription) : Attribute
+public partial class SettingsEntryAttribute(Symbol symbol, string resourceKeyHeader, string? resourceKeyDescription) : Attribute
{
+ public SettingsEntryAttribute(int symbol, string resourceKeyHeader, string? resourceKeyDescription)
+ : this((Symbol)symbol, resourceKeyHeader, resourceKeyDescription)
+ {
+ }
+
public static readonly SettingsEntryAttribute AutoUpdate = new(Symbol.Communication,
nameof(SettingsPageResources.DownloadUpdateAutomaticallyEntryHeader),
nameof(SettingsPageResources.DownloadUpdateAutomaticallyEntryDescription));
@@ -52,24 +57,22 @@ public class SettingsEntryAttribute(Symbol symbol, string resourceKeyHeader, str
typeof(AppSettings).GetProperties(BindingFlags.Instance | BindingFlags.Public)
.SelectNotNull(f => f.GetCustomAttribute()));
- private static Type ResourceLoader => typeof(SettingsPageResources);
-
public Symbol Symbol { get; } = symbol;
public string LocalizedResourceHeader { get; } =
- LocalizedResourceAttributeHelper.GetLocalizedResourceContent(ResourceLoader, resourceKeyHeader) ?? "";
+ SettingsPageResources.GetResource(resourceKeyHeader);
public string LocalizedResourceDescription { get; } =
resourceKeyDescription is null
? ""
- : LocalizedResourceAttributeHelper.GetLocalizedResourceContent(ResourceLoader, resourceKeyDescription) ?? "";
+ : SettingsPageResources.GetResource(resourceKeyDescription);
public static SettingsEntryAttribute GetFromPropertyName(string propertyName)
{
return GetFromPropertyName(propertyName);
}
- public static SettingsEntryAttribute GetFromPropertyName(string propertyName)
+ public static SettingsEntryAttribute GetFromPropertyName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string propertyName)
{
return typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public)
?.GetCustomAttribute() ??
diff --git a/src/Pixeval/Controls/DocumentViewer/DocumentViewerViewModel.cs b/src/Pixeval/Controls/DocumentViewer/DocumentViewerViewModel.cs
index fa344d72..4c0dc46c 100644
--- a/src/Pixeval/Controls/DocumentViewer/DocumentViewerViewModel.cs
+++ b/src/Pixeval/Controls/DocumentViewer/DocumentViewerViewModel.cs
@@ -42,7 +42,7 @@ using QuestPDF.Infrastructure;
namespace Pixeval.Controls;
-public class DocumentViewerViewModel(NovelContent novelContent) : ObservableObject, IDisposable, INovelParserViewModel, INovelParserViewModel
+public partial class DocumentViewerViewModel(NovelContent novelContent) : ObservableObject, IDisposable, INovelParserViewModel, INovelParserViewModel
{
///
/// 需要从外部Invoke
diff --git a/src/Pixeval/Controls/Download/DownloadItemDataProvider.cs b/src/Pixeval/Controls/Download/DownloadItemDataProvider.cs
index 5bd335ac..6cfc9f37 100644
--- a/src/Pixeval/Controls/Download/DownloadItemDataProvider.cs
+++ b/src/Pixeval/Controls/Download/DownloadItemDataProvider.cs
@@ -7,7 +7,7 @@ using Pixeval.Download.Models;
namespace Pixeval.Controls;
-public class DownloadItemDataProvider : ObservableObject, IDisposable
+public partial class DownloadItemDataProvider : ObservableObject, IDisposable
{
public AdvancedObservableCollection View { get; } = [];
diff --git a/src/Pixeval/Controls/Download/DownloadItemViewModel.cs b/src/Pixeval/Controls/Download/DownloadItemViewModel.cs
index c569aeaf..82ed931b 100644
--- a/src/Pixeval/Controls/Download/DownloadItemViewModel.cs
+++ b/src/Pixeval/Controls/Download/DownloadItemViewModel.cs
@@ -36,7 +36,7 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.Controls;
///
-public sealed class DownloadItemViewModel : WorkEntryViewModel
+public sealed partial class DownloadItemViewModel : WorkEntryViewModel
{
public DownloadTaskBase DownloadTask { get; }
diff --git a/src/Pixeval/Controls/Download/DownloadListOption.cs b/src/Pixeval/Controls/Download/DownloadListOption.cs
index 55bc4f0d..66f58032 100644
--- a/src/Pixeval/Controls/Download/DownloadListOption.cs
+++ b/src/Pixeval/Controls/Download/DownloadListOption.cs
@@ -22,6 +22,7 @@ using Pixeval.Attributes;
namespace Pixeval.Controls;
+[LocalizationMetadata(typeof(DownloadPageResources))]
public enum DownloadListOption
{
[LocalizedResource(typeof(DownloadPageResources), nameof(DownloadPageResources.DownloadListOptionAllQueued))]
diff --git a/src/Pixeval/Controls/Entry/EntryViewViewModel.cs b/src/Pixeval/Controls/Entry/EntryViewViewModel.cs
index 24e29eb6..19fa7dbc 100644
--- a/src/Pixeval/Controls/Entry/EntryViewViewModel.cs
+++ b/src/Pixeval/Controls/Entry/EntryViewViewModel.cs
@@ -19,6 +19,7 @@
#endregion
using System;
+using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using Pixeval.Collections;
using Pixeval.CoreApi.Engine;
@@ -26,7 +27,7 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.Controls;
-public abstract class EntryViewViewModel
+public abstract class EntryViewViewModel
: ObservableObject, IDisposable
where T : class, IIdEntry
where TViewModel : EntryViewModel, IViewModelFactory
diff --git a/src/Pixeval/Controls/Entry/IDataProvider.cs b/src/Pixeval/Controls/Entry/IDataProvider.cs
index 5f524f8b..42def2ff 100644
--- a/src/Pixeval/Controls/Entry/IDataProvider.cs
+++ b/src/Pixeval/Controls/Entry/IDataProvider.cs
@@ -20,6 +20,7 @@
using System;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.WinUI.Collections;
using Pixeval.Collections;
using Pixeval.CoreApi.Engine;
@@ -27,7 +28,7 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.Controls;
-public interface IDataProvider
+public interface IDataProvider
: INotifyPropertyChanged, INotifyPropertyChanging, IDisposable
where T : class, IIdEntry
where TViewModel : class, IViewModelFactory
diff --git a/src/Pixeval/Controls/Entry/SharableViewDataProvider.cs b/src/Pixeval/Controls/Entry/SharableViewDataProvider.cs
index 92c1ec8b..c4fc5a83 100644
--- a/src/Pixeval/Controls/Entry/SharableViewDataProvider.cs
+++ b/src/Pixeval/Controls/Entry/SharableViewDataProvider.cs
@@ -21,6 +21,7 @@
#endregion
using System;
+using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.Collections;
using Pixeval.Collections;
@@ -34,7 +35,7 @@ namespace Pixeval.Controls;
/// 复用时调用,和会在所有复用对象都Dispose时Dispose
/// 初始化时调用
///
-public class SharableViewDataProvider
+public partial class SharableViewDataProvider
: ObservableObject, IDataProvider, IDisposable
where T : class, IIdEntry
where TViewModel : EntryViewModel, IViewModelFactory, IDisposable
diff --git a/src/Pixeval/Controls/Entry/SimpleViewDataProvider.cs b/src/Pixeval/Controls/Entry/SimpleViewDataProvider.cs
index a13539f8..edc65d5a 100644
--- a/src/Pixeval/Controls/Entry/SimpleViewDataProvider.cs
+++ b/src/Pixeval/Controls/Entry/SimpleViewDataProvider.cs
@@ -19,6 +19,7 @@
#endregion
using System;
+using System.Diagnostics.CodeAnalysis;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.Collections;
using Pixeval.Collections;
@@ -27,7 +28,7 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.Controls;
-public class SimpleViewDataProvider : ObservableObject, IDataProvider
+public partial class SimpleViewDataProvider : ObservableObject, IDataProvider
where T : class, IIdEntry
where TViewModel : class, IViewModelFactory, IDisposable
{
diff --git a/src/Pixeval/Controls/EnumComboBox.cs b/src/Pixeval/Controls/EnumComboBox.cs
index 0fc81404..ec5b6b9b 100644
--- a/src/Pixeval/Controls/EnumComboBox.cs
+++ b/src/Pixeval/Controls/EnumComboBox.cs
@@ -25,14 +25,12 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Markup;
using Pixeval.CoreApi.Global.Enum;
-using Pixeval.Util;
using WinUI3Utilities;
using WinUI3Utilities.Attributes;
namespace Pixeval.Controls;
[DependencyProperty