AOT部分支持 (#502)

* 替换WebApiClientCore

* 添加sg优化

* 公开类型

* 修复图标错位

* 更新AOT支持

* 暂存

* 解决项目内json反射问题

* set pubxml

* 修复枚举和资源相关的反射

* fix all CSWinrt1028

* 修复收藏bug

* asynclazy可重入性

* 修复novel动画

* 调整SettingsEntryAttribute

* 添加aot条件

* 修复备份

* update workflow
This commit is contained in:
Poker 2024-06-15 14:33:52 +08:00 committed by GitHub
parent ee70b396de
commit 356ef9326f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
148 changed files with 1320 additions and 726 deletions

View File

@ -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 `

2
.gitignore vendored
View File

@ -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

View File

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31808.319

View File

@ -35,23 +35,19 @@
<Project Path="src\Pixeval.Controls\Pixeval.Controls.csproj">
<Configuration Solution="*|arm64" Project="*|arm64" />
<Configuration Solution="*|x64" Project="*|x64" />
<Configuration Solution="*|x86" Project="*|x86" />
</Project>
<Project Path="src\Pixeval.CoreApi\Pixeval.CoreApi.csproj">
<Configuration Solution="*|arm64" Project="*|arm64" />
<Configuration Solution="*|x64" Project="*|x64" />
<Configuration Solution="*|x86" Project="*|x86" />
</Project>
<Project Path="src\Pixeval.SourceGen\Pixeval.SourceGen.csproj" />
<Project Path="src\Pixeval.Utilities\Pixeval.Utilities.csproj">
<Configuration Solution="*|arm64" Project="*|arm64" />
<Configuration Solution="*|x64" Project="*|x64" />
<Configuration Solution="*|x86" Project="*|x86" />
</Project>
<Project Path="src\Pixeval\Pixeval.csproj">
<Configuration Solution="*|*" Project="*|*|Deploy" />
<Configuration Solution="*|arm64" Project="*|arm64|Deploy" />
<Configuration Solution="*|x64" Project="*|x64|Deploy" />
<Configuration Solution="*|x86" Project="*|x86|Deploy" />
</Project>
</Solution>

View File

@ -24,6 +24,7 @@ using Pixeval.Attributes;
namespace Pixeval.Controls;
[LocalizationMetadata(typeof(AdvancedItemsViewResources))]
public enum ItemsViewLayoutType
{
[LocalizedResource(typeof(AdvancedItemsViewResources), nameof(AdvancedItemsViewResources.LinedFlow))]

View File

@ -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<T> : IList<T>, IList, IReadOnlyList<T>, INotifyCollectionChanged, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<T> where T : class
public class AdvancedObservableCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T> : IList<T>, IList, IReadOnlyList<T>, INotifyCollectionChanged, INotifyPropertyChanged, ISupportIncrementalLoading, IComparer<T> where T : class
{
private readonly Dictionary<string, PropertyInfo> _sortProperties = [];

View File

@ -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 <http://www.gnu.org/licenses/>.
#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<T>(Type resourceType) : Attribute
{
public Type ResourceType { get; } = resourceType;
}

View File

@ -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;
}

View File

@ -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)
{

View File

@ -7,9 +7,11 @@
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<Nullable>Enable</Nullable>
<Configurations>Debug;Release</Configurations>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefaultLanguage>zh-cn</DefaultLanguage>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
@ -17,20 +19,21 @@
<PackageReference Include="FluentIcons.WinUI" Version="1.1.242" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.2.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.106">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.3233" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240428000" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.240607001" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Collections" Version="8.0.240109" />
<PackageReference Include="CommunityToolkit.WinUI.Triggers" Version="8.1.240606-rc" />
<PackageReference Include="CommunityToolkit.WinUI.Collections" Version="8.1.240606-rc" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.1.240606-rc" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.1.240606-rc" />
<PackageReference Include="WinUI3Utilities" Version="1.1.7" />
<PackageReference Include="WinUI3Utilities" Version="1.1.7.6" />
<ProjectReference Include="..\Pixeval.Utilities\Pixeval.Utilities.csproj" />
<ProjectReference Include="..\Pixeval.SourceGen\Pixeval.SourceGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="False" />
</ItemGroup>
<!--Exposes PRI resources-->

View File

@ -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<T>() : JsonStringEnumConverter<T>(JsonNamingPolicy.SnakeCaseLower) where T : struct, Enum;

View File

@ -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;
/// <typeparam name="TFetchEngine">The fetch engine</typeparam>
public abstract class AbstractPixivAsyncEnumerator<TEntity, TRawEntity, TFetchEngine>(TFetchEngine pixivFetchEngine,
MakoApiKind apiKind) : IAsyncEnumerator<TEntity>
where TFetchEngine : class, IFetchEngine<TEntity>
where TFetchEngine : class, IFetchEngine<TEntity>
where TEntity : class, IEntry
where TRawEntity : class
{
@ -100,13 +101,12 @@ public abstract class AbstractPixivAsyncEnumerator<TEntity, TRawEntity, TFetchEn
return Result<TRawEntity>.AsFailure(await MakoNetworkException.FromHttpResponseMessageAsync(responseMessage, MakoClient.Configuration.DomainFronting).ConfigureAwait(false));
}
var result = (await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false)).FromJson<TRawEntity>();
var str = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
return result is null
? Result<TRawEntity>.AsFailure()
: ValidateResponse(result)
? Result<TRawEntity>.AsSuccess(result)
: Result<TRawEntity>.AsFailure();
if (JsonSerializer.Deserialize(str, typeof(TRawEntity), AppJsonSerializerContext.Default) is TRawEntity result)
if (ValidateResponse(result))
return Result<TRawEntity>.AsSuccess(result);
return Result<TRawEntity>.AsFailure();
}
catch (Exception e)
{

View File

@ -29,9 +29,9 @@ namespace Pixeval.CoreApi.Engine;
internal abstract class RecursivePixivAsyncEnumerator<TEntity, TRawEntity, TFetchEngine>(TFetchEngine pixivFetchEngine, MakoApiKind makoApiKind)
: AbstractPixivAsyncEnumerator<TEntity, TRawEntity, TFetchEngine>(pixivFetchEngine, makoApiKind)
where TEntity : class, IEntry
where TEntity : class, IEntry
where TRawEntity : class
where TFetchEngine : class, IFetchEngine<TEntity>
where TFetchEngine : class, IFetchEngine<TEntity>
{
private TRawEntity? RawEntity { get; set; }
@ -112,9 +112,9 @@ internal static class RecursivePixivAsyncEnumerators
{
public abstract class BaseRecursivePixivAsyncEnumerator<TEntity, TRawEntity, TFetchEngine>(TFetchEngine pixivFetchEngine, MakoApiKind makoApiKind, string initialUrl)
: RecursivePixivAsyncEnumerator<TEntity, TRawEntity, TFetchEngine>(pixivFetchEngine, makoApiKind)
where TEntity : class, IEntry
where TRawEntity : PixivNextUrlResponse<TEntity>
where TFetchEngine : class, IFetchEngine<TEntity>
where TEntity : class, IEntry
where TRawEntity : class, IPixivNextUrlResponse<TEntity>
where TFetchEngine : class, IFetchEngine<TEntity>
{
protected override string InitialUrl => initialUrl;

View File

@ -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 <see cref="Private" /> option
/// is only permitted when the ID is pointing to yourself
/// </summary>
[JsonConverter(typeof(SnakeCaseLowerEnumConverter<PrivacyPolicy>))]
public enum PrivacyPolicy
{
[Description("public")]
[EnumMember(Value = "public")]
Public,
[Description("private")]
[EnumMember(Value = "private")]
Private
}

View File

@ -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
];
}

View File

@ -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<TargetFilter>))]
public enum TargetFilter
{
[Description("for_android")]
[EnumMember(Value = "for_android")]
ForAndroid,
[Description("for_ios")]
[EnumMember(Value = "for_ios")]
ForIos
}

View File

@ -95,7 +95,7 @@ public partial class MakoClient
var span = contentHtml[startIndex..endIndex];
return JsonSerializer.Deserialize<NovelContent>(span)!;
return (NovelContent)JsonSerializer.Deserialize(span, typeof(NovelContent), AppJsonSerializerContext.Default)!;
});
/// <summary>

View File

@ -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<HttpResponseMessage> RunWithLoggerAsync(Func<Task<HttpResponseMessage>> task)
private async Task<HttpResponseMessage> RunWithLoggerAsync(Func<Task<HttpResponseMessage>> 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<Func<IWebProxy>>();
_getCurrentSystemProxy = @delegate ?? ThrowUtils.Throw<MissingMethodException, Func<IWebProxy>>("Unable to find proxy functions");
_getCurrentSystemProxy = @delegate ?? ThrowUtils.Throw<MissingMethodException, Func<IWebProxy>>(new("Unable to find proxy functions"));
HttpClient.DefaultProxy = _getCurrentSystemProxy();
}

View File

@ -64,8 +64,9 @@ public partial class MakoClient : ICancellable, IDisposable, IAsyncDisposable
makoClient.Session = (await makoClient.Provider.GetRequiredService<IAuthEndPoint>().RefreshAsync(new RefreshSessionRequest(refreshToken)).ConfigureAwait(false)).ToSession();
return makoClient;
}
catch
catch (Exception e)
{
logger.LogError("Login error", e);
await makoClient.DisposeAsync();
return null;
}

View File

@ -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; }

View File

@ -301,8 +301,8 @@ internal class SpecialDictionaryConverter<T> : JsonConverter<T[]>
return ThrowUtils.Json<T[]>();
case JsonTokenType.PropertyName:
{
var jsonConverter = (JsonConverter<T>)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:

View File

@ -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;
}

View File

@ -53,7 +53,7 @@ public interface IAppApiEndPoint
Task<PixivSingleNovelResponse> GetSingleNovelAsync([AliasAs("novel_id")] long id);
[HttpGet("/webview/v2/novel")]
Task<string> GetNovelContentAsync([AliasAs("id")] long id, [AliasAs("raw")] bool raw = false);
Task<string> 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<PixivRelatedUsersResponse> RelatedUserAsync([AliasAs("filter")] TargetFilter filter, [AliasAs("seed_user_id")] long userId);
Task<PixivRelatedUsersResponse> RelatedUserAsync(TargetFilter filter, [AliasAs("seed_user_id")] long userId);
[HttpPost("/v1/user/follow/add")]
Task<HttpResponseMessage> FollowUserAsync([FormContent] FollowUserRequest request);
@ -77,10 +77,10 @@ public interface IAppApiEndPoint
Task<HttpResponseMessage> RemoveFollowUserAsync([FormContent] RemoveFollowUserRequest request);
[HttpGet("/v1/trending-tags/illust")]
Task<TrendingTagResponse> GetTrendingTagsAsync([AliasAs("filter")] TargetFilter filter);
Task<TrendingTagResponse> GetTrendingTagsAsync(TargetFilter filter);
[HttpGet("/v1/trending-tags/novel")]
Task<TrendingTagResponse> GetTrendingTagsForNovelAsync([AliasAs("filter")] TargetFilter filter);
Task<TrendingTagResponse> GetTrendingTagsForNovelAsync(TargetFilter filter);
[HttpGet("/v1/ugoira/metadata")]
Task<UgoiraMetadataResponse> GetUgoiraMetadataAsync([AliasAs("illust_id")] long id);

View File

@ -26,8 +26,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivBookmarkTagResponse : PixivNextUrlResponse<BookmarkTag>
public partial record PixivBookmarkTagResponse : IPixivNextUrlResponse<BookmarkTag>
{
[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; } = [];
}

View File

@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivCommentResponse : PixivNextUrlResponse<Comment>
public partial record PixivCommentResponse : IPixivNextUrlResponse<Comment>
{
[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; } = [];
}

View File

@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivIllustrationResponse : PixivNextUrlResponse<Illustration>
public partial record PixivIllustrationResponse : IPixivNextUrlResponse<Illustration>
{
[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; } = [];
}

View File

@ -25,10 +25,10 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
public abstract record PixivNextUrlResponse<TEntity> where TEntity : class, IEntry
public interface IPixivNextUrlResponse<TEntity> 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; }
}

View File

@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivNovelResponse : PixivNextUrlResponse<Novel>
public partial record PixivNovelResponse : IPixivNextUrlResponse<Novel>
{
[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; } = [];
}

View File

@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivSpotlightResponse : PixivNextUrlResponse<Spotlight>
public partial record PixivSpotlightResponse : IPixivNextUrlResponse<Spotlight>
{
[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; } = [];
}

View File

@ -24,8 +24,11 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.CoreApi.Net.Response;
[Factory]
public partial record PixivUserResponse : PixivNextUrlResponse<User>
public partial record PixivUserResponse : IPixivNextUrlResponse<User>
{
[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; } = [];
}

View File

@ -6,13 +6,13 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="WebApiClientCore" Version="2.1.1" PrivateAssets="True" />
<PackageReference Include="WebApiClientCore.Extensions.SourceGenerator" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

View File

@ -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);
}

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
#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<StatementSyntax>([
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<StatementSyntax>([
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<string, string>"),
"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<string, string>"),
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<AttributeData> 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<AttributeData> 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);
});
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
#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<GeneratorAttributeSyntaxContext> gas, out string fileName)
{
var dictionary = new Dictionary<ITypeSymbol, List<INamedTypeSymbol>>(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<global::Pixeval.Download.MacroParser.IMacro>"),
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);
});
}
}

View File

@ -6,16 +6,17 @@
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<IsRoslynComponent>true</IsRoslynComponent>
<PlatformTarget>AnyCPU</PlatformTarget>
<Configurations>Debug;Release</Configurations>
<Configurations>Debug;Release</Configurations>
<PlatformTarget>AnyCPU</PlatformTarget>
<LangVersion>latest</LangVersion>
<PublishAot>false</PublishAot>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.14.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
</Project>

View File

@ -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 <http://www.gnu.org/licenses/>.
#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<GeneratorAttributeSyntaxContext> 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}<string, global::{AttributeFullName}>"),
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<string, global::{AttributeFullName}>"),
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);
});
}
}

View File

@ -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);
}
/// <summary>
/// 缩进伪tab
/// </summary>
/// <param name="n">tab数量</param>
/// <returns>4n个space</returns>
internal static string Spacing(int n)
{
var temp = "";
for (var i = 0; i < n; ++i)
temp += " ";
return temp;
}
/// <summary>
/// Generate the following code
/// <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));
}
/// <summary>
/// Generate the following code
/// <code>
/// nameof(<paramref name="name" />)
/// </code>
/// </summary>
/// <returns>NameOfExpression</returns>
internal static InvocationExpressionSyntax NameOfExpression(string name) => NameOfExpression(IdentifierName(name));
/// <summary>
/// Generate the following code
/// <code>
/// nameof(<paramref name="expressionSyntax" />)
/// </code>
/// </summary>
/// <returns>NameOfExpression</returns>
internal static InvocationExpressionSyntax NameOfExpression(ExpressionSyntax expressionSyntax) => InvocationExpression(IdentifierName("nameof"), ArgumentList().AddArguments(Argument(expressionSyntax)));
internal static IEnumerable<IPropertySymbol> GetProperties(this ITypeSymbol typeSymbol, INamedTypeSymbol attribute)
{
@ -114,31 +112,6 @@ public static class SyntaxHelper
return false;
}
/// <summary>
/// 获取某<paramref name="symbol"/>的namespace并加入<paramref name="namespaces"/>集合
/// </summary>
/// <param name="namespaces">namespaces集合</param>
/// <param name="usedTypes">已判断过的types</param>
/// <param name="contextType">上下文所在的类</param>
/// <param name="symbol">type的symbol</param>
internal static void UseNamespace(this HashSet<string> namespaces, HashSet<ITypeSymbol> 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);
}
/// <summary>
/// Generate the following code
/// <code>
@ -163,21 +136,21 @@ public static class SyntaxHelper
/// <summary>
/// Generate the following code
/// <code>
/// partial <paramref name="symbol" /> <paramref name="name" /><br/>
/// partial <paramref name="originalSymbol" /> <paramref name="name" /><br/>
/// {<br/>
/// <paramref name="member" /><br/>
/// }
/// </code>
/// </summary>
/// <returns>TypeDeclaration</returns>
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))

View File

@ -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<T>
/// </summary>
private T? _value;
private readonly SemaphoreSlim _slimLock = new(1, 1);
public AsyncLazy(T value)
{
_value = value;
@ -52,7 +55,18 @@ public class AsyncLazy<T>
public ValueTask<T> ValueAsync => ValueAsyncInternal();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private async ValueTask<T> ValueAsyncInternal() => IsValueCreated ? _value! : await CreateValueAsync();
private async ValueTask<T> ValueAsyncInternal()
{
await _slimLock.WaitAsync();
try
{
return IsValueCreated ? _value! : await CreateValueAsync();
}
finally
{
_ = _slimLock.Release();
}
}
private async Task<T> CreateValueAsync()
{

View File

@ -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<TEnum>(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<string>("Attribute not found");
}

View File

@ -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<string?> ToJsonAsync<TEntity>(this TEntity? obj, Action<JsonSerializerOptions>? 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<JsonSerializerOptions>? serializerOptionConfigure = null)
{
return obj?.Let(o => JsonSerializer.Serialize(o, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option))));
}
public static async ValueTask<TEntity?> FromJsonAsync<TEntity>(this IMemoryOwner<byte> bytes, Action<JsonSerializerOptions>? serializerOptionConfigure = null)
{
using (bytes)
{
await using var stream = bytes.Memory.AsStream();
return await JsonSerializer.DeserializeAsync<TEntity>(stream, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option))).ConfigureAwait(false);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TEntity? FromJson<TEntity>(this string str, Action<JsonSerializerOptions>? serializerOptionConfigure = null)
{
return JsonSerializer.Deserialize<TEntity>(str, new JsonSerializerOptions().Apply(option => serializerOptionConfigure?.Invoke(option)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsIgnoreCase(this string str1, string str2)
{

View File

@ -5,6 +5,7 @@
<Platforms>x86;x64;arm64</Platforms>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<IsTrimmable>true</IsTrimmable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.2.2" />

View File

@ -39,8 +39,8 @@ public static class ThrowUtils
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static TResult Throw<TException, TResult>(params object[] parameters) where TException : Exception
=> throw ((Exception)Activator.CreateInstance(typeof(TException), parameters)!);
public static TResult Throw<TException, TResult>(TException e) where TException : Exception
=> throw e;
/// <exception cref="InvalidOperationException"/>
[DoesNotReturn]

View File

@ -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);

View File

@ -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<string>))]
public partial class SettingsSerializeContext : JsonSerializerContext;

View File

@ -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<GitHubRelease[]>("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<AppReleaseModel>(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; }

View File

@ -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;

View File

@ -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<SettingsEntryAttribute>()));
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<AppSettings>(propertyName);
}
public static SettingsEntryAttribute GetFromPropertyName<T>(string propertyName)
public static SettingsEntryAttribute GetFromPropertyName<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string propertyName)
{
return typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public)
?.GetCustomAttribute<SettingsEntryAttribute>() ??

View File

@ -42,7 +42,7 @@ using QuestPDF.Infrastructure;
namespace Pixeval.Controls;
public class DocumentViewerViewModel(NovelContent novelContent) : ObservableObject, IDisposable, INovelParserViewModel<SoftwareBitmapSource>, INovelParserViewModel<Stream>
public partial class DocumentViewerViewModel(NovelContent novelContent) : ObservableObject, IDisposable, INovelParserViewModel<SoftwareBitmapSource>, INovelParserViewModel<Stream>
{
/// <summary>
/// 需要从外部Invoke

View File

@ -7,7 +7,7 @@ using Pixeval.Download.Models;
namespace Pixeval.Controls;
public class DownloadItemDataProvider : ObservableObject, IDisposable
public partial class DownloadItemDataProvider : ObservableObject, IDisposable
{
public AdvancedObservableCollection<DownloadItemViewModel> View { get; } = [];

View File

@ -36,7 +36,7 @@ using Pixeval.CoreApi.Model;
namespace Pixeval.Controls;
/// <inheritdoc/>
public sealed class DownloadItemViewModel : WorkEntryViewModel<IWorkEntry>
public sealed partial class DownloadItemViewModel : WorkEntryViewModel<IWorkEntry>
{
public DownloadTaskBase DownloadTask { get; }

View File

@ -22,6 +22,7 @@ using Pixeval.Attributes;
namespace Pixeval.Controls;
[LocalizationMetadata(typeof(DownloadPageResources))]
public enum DownloadListOption
{
[LocalizedResource(typeof(DownloadPageResources), nameof(DownloadPageResources.DownloadListOptionAllQueued))]

View File

@ -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<T, TViewModel>
public abstract class EntryViewViewModel<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TViewModel>
: ObservableObject, IDisposable
where T : class, IIdEntry
where TViewModel : EntryViewModel<T>, IViewModelFactory<T, TViewModel>

View File

@ -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<T, TViewModel>
public interface IDataProvider<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TViewModel>
: INotifyPropertyChanged, INotifyPropertyChanging, IDisposable
where T : class, IIdEntry
where TViewModel : class, IViewModelFactory<T, TViewModel>

View File

@ -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;
/// 复用时调用<see cref="CloneRef"/><see cref="FetchEngineRef"/>和<see cref="EntrySourceRef"/>会在所有复用对象都Dispose时Dispose<br/>
/// 初始化时调用<see cref="ResetEngine"/>
/// </summary>
public class SharableViewDataProvider<T, TViewModel>
public partial class SharableViewDataProvider<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TViewModel>
: ObservableObject, IDataProvider<T, TViewModel>, IDisposable
where T : class, IIdEntry
where TViewModel : EntryViewModel<T>, IViewModelFactory<T, TViewModel>, IDisposable

View File

@ -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<T, TViewModel> : ObservableObject, IDataProvider<T, TViewModel>
public partial class SimpleViewDataProvider<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TViewModel> : ObservableObject, IDataProvider<T, TViewModel>
where T : class, IIdEntry
where TViewModel : class, IViewModelFactory<T, TViewModel>, IDisposable
{

View File

@ -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<object>("SelectedEnum", DependencyPropertyDefaultValue.Default, nameof(OnSelectedEnumChanged), IsNullable = true)]
[DependencyProperty<Array>("EnumSource", DependencyPropertyDefaultValue.Default, nameof(OnEnumSourceChanged))]
public sealed partial class EnumComboBox : ComboBox
{
public new event EventHandler<SelectionChangedEventArgs>? SelectionChanged;
@ -41,6 +39,12 @@ public sealed partial class EnumComboBox : ComboBox
{
Style = Application.Current.Resources["DefaultComboBoxStyle"] as Style;
base.SelectionChanged += ComboBox_SelectionChanged;
var token = RegisterPropertyChangedCallback(ItemsSourceProperty, (sender, _) =>
{
if (sender is EnumComboBox { ItemsSource: IEnumerable<StringRepresentableItem> enumerable } box)
box.SelectedItem = enumerable.FirstOrDefault();
});
Unloaded += (sender, _) => sender.To<DependencyObject>().UnregisterPropertyChangedCallback(ItemsSourceProperty, token);
}
public bool RaiseEventAfterLoaded { get; set; } = true;
@ -65,33 +69,23 @@ public sealed partial class EnumComboBox : ComboBox
var comboBox = sender.To<EnumComboBox>();
comboBox.SelectedItem = comboBox.ItemsSource?.To<IEnumerable<StringRepresentableItem>>().FirstOrDefault(r => Equals(r.Item, comboBox.SelectedEnum));
}
private static void OnEnumSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var comboBox = sender.To<EnumComboBox>();
comboBox.ItemsSource = LocalizedResourceAttributeHelper.GetLocalizedResourceContents(comboBox.EnumSource);
if (comboBox.SelectedEnum is null)
comboBox.SelectedEnum = comboBox.EnumSource.GetValue(0);
else
comboBox.SelectedItem = comboBox.ItemsSource.To<IEnumerable<StringRepresentableItem>>().FirstOrDefault(r => Equals(r.Item, comboBox.SelectedEnum));
}
}
[MarkupExtensionReturnType(ReturnType = typeof(Array))]
public sealed class EnumValuesExtension : MarkupExtension
[MarkupExtensionReturnType(ReturnType = typeof(IReadOnlyList<StringRepresentableItem>))]
public sealed partial class EnumValuesExtension : MarkupExtension
{
public EnumValuesEnum Type { get; set; }
protected override object ProvideValue()
{
return Enum.GetValues(Type switch
return Type switch
{
EnumValuesEnum.WorkType => typeof(WorkType),
EnumValuesEnum.SimpleWorkType => typeof(SimpleWorkType),
EnumValuesEnum.WorkSortOption => typeof(WorkSortOption),
EnumValuesEnum.PrivacyPolicy => typeof(PrivacyPolicy),
EnumValuesEnum.WorkType => WorkTypeExtension.GetItems(),
EnumValuesEnum.SimpleWorkType => SimpleWorkTypeExtension.GetItems(),
EnumValuesEnum.WorkSortOption => WorkSortOptionExtension.GetItems(),
EnumValuesEnum.PrivacyPolicy => PrivacyPolicyExtension.GetItems(),
_ => throw new ArgumentOutOfRangeException()
});
};
}
}

View File

@ -24,6 +24,7 @@ using System.Threading.Tasks;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Pixeval.CoreApi;
using Pixeval.CoreApi.Global.Enum;
using Pixeval.CoreApi.Net.Response;
using WinUI3Utilities;
@ -79,7 +80,7 @@ public sealed partial class CommentRepliesBlock
private async Task AddComment(HttpResponseMessage postCommentResponse)
{
if (postCommentResponse.IsSuccessStatusCode && await postCommentResponse.Content.ReadFromJsonAsync<PostCommentResponse>() is { Comment: { } comment })
if (postCommentResponse.IsSuccessStatusCode && await postCommentResponse.Content.ReadFromJsonAsync(typeof(PostCommentResponse), AppJsonSerializerContext.Default) is PostCommentResponse { Comment: { } comment })
ViewModel.AddComment(comment);
}
}

View File

@ -25,7 +25,7 @@ using IllustrationViewDataProvider = Pixeval.Controls.SharableViewDataProvider<
namespace Pixeval.Controls;
public sealed class IllustrationViewViewModel : SortableEntryViewViewModel<Illustration, IllustrationItemViewModel>
public sealed partial class IllustrationViewViewModel : SortableEntryViewViewModel<Illustration, IllustrationItemViewModel>
{
public IllustrationViewViewModel(IllustrationViewViewModel viewModel) : this(viewModel.DataProvider.CloneRef())
{

View File

@ -25,7 +25,7 @@ using IllustratorViewDataProvider = Pixeval.Controls.SimpleViewDataProvider<
namespace Pixeval.Controls;
public sealed class IllustratorViewViewModel : EntryViewViewModel<User, IllustratorItemViewModel>
public sealed partial class IllustratorViewViewModel : EntryViewViewModel<User, IllustratorItemViewModel>
{
public override IllustratorViewDataProvider DataProvider { get; } = new();

View File

@ -16,6 +16,10 @@
PointerExited="NovelItem_OnPointerExited"
mc:Ignorable="d">
<Grid.Resources>
<ExponentialEase
x:Key="EasingFunction"
EasingMode="EaseOut"
Exponent="12" />
<!-- ReSharper disable once Xaml.RedundantResource -->
<Storyboard x:Key="ThumbnailStoryboard">
<DoubleAnimation

View File

@ -27,7 +27,7 @@ using NovelViewDataProvider = Pixeval.Controls.SharableViewDataProvider<
namespace Pixeval.Controls;
public sealed class NovelViewViewModel : SortableEntryViewViewModel<Novel, NovelItemViewModel>
public sealed partial class NovelViewViewModel : SortableEntryViewViewModel<Novel, NovelItemViewModel>
{
public NovelViewViewModel(NovelViewViewModel viewModel) : this(viewModel.DataProvider.CloneRef())
{

View File

@ -18,7 +18,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
@ -26,13 +25,12 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Windows.Foundation;
using CommunityToolkit.WinUI;
using Pixeval.Util;
using WinUI3Utilities;
using WinUI3Utilities.Attributes;
namespace Pixeval.Controls;
[DependencyProperty<Array>("ItemsSource", DependencyPropertyDefaultValue.Default, nameof(OnItemsSourceChanged))]
[DependencyProperty<object>("ItemsSource", DependencyPropertyDefaultValue.Default, nameof(OnItemsSourceChanged))]
[DependencyProperty<object>("SelectedItem", propertyChanged: nameof(OnSelectedItemChanged))]
[DependencyProperty<object>("Header")]
public sealed partial class SettingRadioButtons : UserControl
@ -60,7 +58,7 @@ public sealed partial class SettingRadioButtons : UserControl
{
var buttons = sender.To<SettingRadioButtons>();
buttons.Buttons.ItemsSource = LocalizedResourceAttributeHelper.GetLocalizedResourceContents(buttons.ItemsSource);
buttons.Buttons.ItemsSource = buttons.ItemsSource;
}
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

View File

@ -16,7 +16,7 @@
<fluent:SymbolIcon Symbol="{x:Bind Entry.HeaderIcon}" />
</controls:SettingsCard.HeaderIcon>
<controls1:EnumComboBox
EnumSource="{x:Bind Entry.EnumValues}"
ItemsSource="{x:Bind Entry.EnumItems}"
SelectedEnum="{x:Bind Entry.Value, Mode=TwoWay}"
SelectionChanged="EnumComboBox_OnSelectionChanged"
Style="{StaticResource SettingsEnumComboBoxStyle}" />

View File

@ -16,7 +16,7 @@
<fluent:SymbolIcon Symbol="{x:Bind Entry.HeaderIcon}" />
</controls:SettingsExpander.HeaderIcon>
<controls2:EnumComboBox
EnumSource="{x:Bind Entry.EnumValues}"
ItemsSource="{x:Bind Entry.EnumItems}"
SelectedEnum="{x:Bind Entry.Value, Mode=TwoWay}"
SelectionChanged="EnumComboBox_OnSelectionChanged"
Style="{StaticResource SettingsEnumComboBoxStyle}" />

View File

@ -24,7 +24,7 @@ using Pixeval.Util;
namespace Pixeval.Controls;
public class SpotlightItemViewModel : ThumbnailEntryViewModel<Spotlight>, IViewModelFactory<Spotlight, SpotlightItemViewModel>
public partial class SpotlightItemViewModel : ThumbnailEntryViewModel<Spotlight>, IViewModelFactory<Spotlight, SpotlightItemViewModel>
{
public static SpotlightItemViewModel CreateInstance(Spotlight entry) => new(entry);

View File

@ -25,7 +25,7 @@ using SpotlightViewDataProvider = Pixeval.Controls.SimpleViewDataProvider<
namespace Pixeval.Controls;
public sealed class SpotlightViewViewModel : EntryViewViewModel<Spotlight, SpotlightItemViewModel>
public sealed partial class SpotlightViewViewModel : EntryViewViewModel<Spotlight, SpotlightItemViewModel>
{
public override SpotlightViewDataProvider DataProvider { get; } = new();

View File

@ -20,6 +20,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.Collections;
@ -29,7 +30,7 @@ using Pixeval.Utilities;
namespace Pixeval.Controls;
public abstract partial class SortableEntryViewViewModel<T, TViewModel>
public abstract partial class SortableEntryViewViewModel<T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TViewModel>
: EntryViewViewModel<T, TViewModel>, ISortableEntryViewViewModel
where T : class, IWorkEntry
where TViewModel : EntryViewModel<T>, IViewModelFactory<T, TViewModel>, IWorkViewModel

View File

@ -63,8 +63,7 @@ public sealed partial class WorkView : IEntryView<ISortableEntryViewViewModel>
{
ArgumentNullException.ThrowIfNull(ViewModel);
if (await viewModel.TryLoadThumbnailAsync(ViewModel))
// TODO 不知道为什么NovelItem的Resource会有问题
if (sender is IllustrationItem && sender.IsFullyOrPartiallyVisible(this))
if (sender.IsFullyOrPartiallyVisible(this))
sender.GetResource<Storyboard>("ThumbnailStoryboard").Begin();
else
sender.Opacity = 1;

View File

@ -33,7 +33,7 @@ using Pixeval.Utilities.Threading;
namespace Pixeval.Download;
public class DownloadManager<TDownloadTask> : IDisposable where TDownloadTask : DownloadTaskBase
public partial class DownloadManager<TDownloadTask> : IDisposable where TDownloadTask : DownloadTaskBase
{
private readonly Channel<TDownloadTask> _downloadTaskChannel;
private readonly HttpClient _httpClient;

View File

@ -23,7 +23,7 @@ using Pixeval.Utilities.Threading;
namespace Pixeval.Download;
public class DownloadStartingDeferral : IDisposable
public partial class DownloadStartingDeferral : IDisposable
{
/// <summary>
/// Set its result to <see langword="true" /> if you want the download to proceed, otherwise, <see langword="false" />

View File

@ -18,7 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Linq;
using System.Collections.Generic;
using Pixeval.Controls;
using Pixeval.Download.MacroParser;
using Pixeval.Download.Macros;
@ -30,9 +30,9 @@ public class IllustrationMetaPathParser : IMetaPathParser<IllustrationItemViewMo
{
private readonly MacroParser<IllustrationItemViewModel> _parser = new();
public static IMacro[] MacroProviderStatic { get; } = MetaPathMacroAttributeHelper.GetAttachedTypeInstances<IllustrationItemViewModel>().ToArray();
public static IReadOnlyList<IMacro> MacroProviderStatic { get; } = MetaPathMacroAttributeHelper.GetIWorkViewModelInstances();
public IMacro[] MacroProvider => MacroProviderStatic;
public IReadOnlyList<IMacro> MacroProvider => MacroProviderStatic;
public string Reduce(string raw, IllustrationItemViewModel context)
{

View File

@ -18,9 +18,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
namespace Pixeval.Download.MacroParser.Ast;
public interface IMetaPathNode<in TContext>
{
string Evaluate(IMacro[] env, TContext context);
string Evaluate(IReadOnlyList<IMacro> env, TContext context);
}

View File

@ -18,6 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Diagnostics;
using Pixeval.Utilities;
using WinUI3Utilities;
@ -28,7 +29,7 @@ namespace Pixeval.Download.MacroParser.Ast;
public record Macro<TContext>(PlainText<TContext> MacroName, OptionalMacroParameter<TContext>? OptionalParameters, bool IsNot)
: SingleNode<TContext>
{
public override string Evaluate(IMacro[] env, TContext context)
public override string Evaluate(IReadOnlyList<IMacro> env, TContext context)
{
var result = env.TryResolve(MacroName.Text, IsNot);
return (result) switch

View File

@ -18,6 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Diagnostics;
namespace Pixeval.Download.MacroParser.Ast;
@ -25,7 +26,7 @@ namespace Pixeval.Download.MacroParser.Ast;
[DebuggerDisplay("{Content}")]
public record OptionalMacroParameter<TContext>(Sequence<TContext> Content) : IMetaPathNode<TContext>
{
public string Evaluate(IMacro[] env, TContext context)
public string Evaluate(IReadOnlyList<IMacro> env, TContext context)
{
return Content.Evaluate(env, context);
}

View File

@ -18,6 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Diagnostics;
namespace Pixeval.Download.MacroParser.Ast;
@ -25,7 +26,7 @@ namespace Pixeval.Download.MacroParser.Ast;
[DebuggerDisplay("{Text}")]
public record PlainText<TContext>(string Text) : SingleNode<TContext>
{
public override string Evaluate(IMacro[] env, TContext context)
public override string Evaluate(IReadOnlyList<IMacro> env, TContext context)
{
return Text;
}

View File

@ -18,6 +18,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
using System.Diagnostics;
namespace Pixeval.Download.MacroParser.Ast;
@ -25,7 +26,7 @@ namespace Pixeval.Download.MacroParser.Ast;
[DebuggerDisplay("{First} {Remains}")]
public record Sequence<TContext>(SingleNode<TContext> First, Sequence<TContext>? Remains) : IMetaPathNode<TContext>
{
public string Evaluate(IMacro[] env, TContext context)
public string Evaluate(IReadOnlyList<IMacro> env, TContext context)
{
return First.Evaluate(env, context) + (Remains?.Evaluate(env, context) ?? string.Empty);
}

View File

@ -18,9 +18,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
namespace Pixeval.Download.MacroParser.Ast;
public abstract record SingleNode<TContext> : IMetaPathNode<TContext>
{
public abstract string Evaluate(IMacro[] env, TContext context);
public abstract string Evaluate(IReadOnlyList<IMacro> env, TContext context);
}

View File

@ -18,11 +18,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#endregion
using System.Collections.Generic;
namespace Pixeval.Download.MacroParser;
public interface IMetaPathParser<in TContext>
{
IMacro[] MacroProvider { get; }
IReadOnlyList<IMacro> MacroProvider { get; }
string Reduce(string raw, TContext context);
}

View File

@ -19,33 +19,8 @@
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Pixeval.Download.MacroParser;
namespace Pixeval.Download.Macros;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class MetaPathMacroAttribute<TContext> : Attribute;
public static class MetaPathMacroAttributeHelper
{
public static IEnumerable<IMacro> GetAttachedTypeInstances()
{
return Assembly.GetExecutingAssembly().GetTypes()
.Where(t => t.GetCustomAttribute(typeof(MetaPathMacroAttribute<>))?.GetType() is not null)
.Select(Activator.CreateInstance)
.OfType<IMacro>();
}
public static IEnumerable<IMacro> GetAttachedTypeInstances<TContext>()
{
return Assembly.GetExecutingAssembly().GetTypes()
.Where(t =>
t.GetCustomAttribute(typeof(MetaPathMacroAttribute<>))?.GetType()
.GenericTypeArguments is [var type] && type.IsAssignableFrom(typeof(TContext)))
.Select(Activator.CreateInstance)
.OfType<IMacro>();
}
}

View File

@ -32,7 +32,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class IllustrationDownloadTask(DownloadHistoryEntry entry, IllustrationItemViewModel illustration)
public partial class IllustrationDownloadTask(DownloadHistoryEntry entry, IllustrationItemViewModel illustration)
: DownloadTaskBase(entry)
{
public override IWorkViewModel ViewModel => IllustrationViewModel;

View File

@ -25,7 +25,7 @@ using Pixeval.Database;
namespace Pixeval.Download.Models;
public sealed class IntrinsicIllustrationDownloadTask : IllustrationDownloadTask
public sealed partial class IntrinsicIllustrationDownloadTask : IllustrationDownloadTask
{
/// <summary>
/// The disposal of <paramref name="stream" /> is not handled

View File

@ -29,7 +29,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public sealed class IntrinsicMangaDownloadTask : MangaDownloadTask
public sealed partial class IntrinsicMangaDownloadTask : MangaDownloadTask
{
public IntrinsicMangaDownloadTask(DownloadHistoryEntry entry, IllustrationItemViewModel illustrationViewModel, IReadOnlyList<Stream> streams) : base(entry, illustrationViewModel)
{

View File

@ -26,7 +26,7 @@ using Pixeval.Database;
namespace Pixeval.Download.Models;
public sealed class IntrinsicNovelDownloadTask : NovelDownloadTask
public sealed partial class IntrinsicNovelDownloadTask : NovelDownloadTask
{
public IntrinsicNovelDownloadTask(DownloadHistoryEntry entry, NovelItemViewModel novel, DocumentViewerViewModel viewModel) :
base(entry, novel, viewModel.NovelContent, viewModel) =>

View File

@ -29,7 +29,7 @@ using SixLabors.ImageSharp;
namespace Pixeval.Download.Models;
public sealed class IntrinsicUgoiraDownloadTask : UgoiraDownloadTask
public sealed partial class IntrinsicUgoiraDownloadTask : UgoiraDownloadTask
{
/// <summary>
/// The disposal of <paramref name="stream" /> is not handled

View File

@ -25,7 +25,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class LazyInitializedIllustrationDownloadTask(DownloadHistoryEntry entry)
public partial class LazyInitializedIllustrationDownloadTask(DownloadHistoryEntry entry)
: IllustrationDownloadTask(entry, null!), ILazyLoadDownloadTask
{
public override async Task DownloadAsync(Downloader downloadStreamAsync)

View File

@ -27,7 +27,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class LazyInitializedMangaDownloadTask(DownloadHistoryEntry entry)
public partial class LazyInitializedMangaDownloadTask(DownloadHistoryEntry entry)
: MangaDownloadTask(entry, null!), ILazyLoadDownloadTask
{
public override async Task DownloadAsync(Downloader downloadStreamAsync)

View File

@ -27,7 +27,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class LazyInitializedNovelDownloadTask(DownloadHistoryEntry entry)
public partial class LazyInitializedNovelDownloadTask(DownloadHistoryEntry entry)
: NovelDownloadTask(entry, null!, null!, null!), ILazyLoadDownloadTask
{
public override async Task DownloadAsync(Downloader downloadStreamAsync)

View File

@ -25,7 +25,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class LazyInitializedUgoiraDownloadTask(DownloadHistoryEntry databaseEntry) : UgoiraDownloadTask(databaseEntry, null!, null!), ILazyLoadDownloadTask
public partial class LazyInitializedUgoiraDownloadTask(DownloadHistoryEntry databaseEntry) : UgoiraDownloadTask(databaseEntry, null!, null!), ILazyLoadDownloadTask
{
public override async Task DownloadAsync(Downloader downloadStreamAsync)
{

View File

@ -30,7 +30,7 @@ using Pixeval.Util.IO;
namespace Pixeval.Download.Models;
public class MangaDownloadTask(DownloadHistoryEntry entry, IllustrationItemViewModel illustration)
public partial class MangaDownloadTask(DownloadHistoryEntry entry, IllustrationItemViewModel illustration)
: IllustrationDownloadTask(entry, illustration)
{
protected int CurrentIndex { get; set; }

View File

@ -36,7 +36,7 @@ using WinUI3Utilities;
namespace Pixeval.Download.Models;
public class NovelDownloadTask : DownloadTaskBase
public partial class NovelDownloadTask : DownloadTaskBase
{
public NovelDownloadTask(
DownloadHistoryEntry entry,

View File

@ -20,6 +20,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Pixeval.Controls;
using Pixeval.CoreApi.Net.Response;
@ -31,7 +32,7 @@ using SixLabors.ImageSharp;
namespace Pixeval.Download.Models;
public class UgoiraDownloadTask(
public partial class UgoiraDownloadTask(
DownloadHistoryEntry entry,
IllustrationItemViewModel illustration,
UgoiraMetadataResponse metadata)
@ -69,13 +70,8 @@ public class UgoiraDownloadTask(
{
if (App.AppViewModel.AppSettings.UgoiraDownloadFormat is UgoiraDownloadFormat.OriginalZip)
{
var dict = new Dictionary<string, Stream>();
for (var i = 0; i < urls.Count; ++i)
{
var extension = Path.GetExtension(urls[i]);
dict.Add($"{i}{extension}", streams[i]);
}
var zipStream = await IoHelper.WriteZipAsync(dict, Dispose);
var names = urls.Select(Path.GetExtension).Select((extension, i) => $"{i}{extension}").ToArray();
var zipStream = await IoHelper.WriteZipAsync(names, streams, Dispose);
await zipStream.StreamSaveToFileAsync(destination);
Report(100);
}

View File

@ -20,7 +20,7 @@
#endregion
using System.Linq;
using System.Collections.Generic;
using Pixeval.Controls;
using Pixeval.Download.MacroParser;
using Pixeval.Download.Macros;
@ -32,9 +32,9 @@ public class NovelMetaPathParser : IMetaPathParser<NovelItemViewModel>
{
private readonly MacroParser<NovelItemViewModel> _parser = new();
public static IMacro[] MacroProviderStatic { get; } = MetaPathMacroAttributeHelper.GetAttachedTypeInstances<NovelItemViewModel>().ToArray();
public static IReadOnlyList<IMacro> MacroProviderStatic { get; } = MetaPathMacroAttributeHelper.GetIWorkViewModelInstances();
public IMacro[] MacroProvider => MacroProviderStatic;
public IReadOnlyList<IMacro> MacroProvider => MacroProviderStatic;
public string Reduce(string raw, NovelItemViewModel context)
{

View File

@ -23,6 +23,7 @@ using Pixeval.Attributes;
namespace Pixeval.Options;
[LocalizationMetadata(typeof(MiscResources))]
public enum FontWeightsOption
{
/// <summary>

View File

@ -24,6 +24,7 @@ using Pixeval.Attributes;
namespace Pixeval.Options;
[LocalizationMetadata(typeof(MiscResources))]
public enum IllustrationDownloadFormat
{
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.Jpg))]

View File

@ -28,6 +28,7 @@ namespace Pixeval.Options;
/// We require a strict matching between the value of the enum member and the order of the
/// <see cref="NavigationViewItem" />in <see cref="MainPage" />
/// </summary>
[LocalizationMetadata(typeof(MainPageResources))]
public enum MainPageTabItem
{
[LocalizedResource(typeof(MainPageResources), nameof(MainPageResources.RecommendationsTabContent))]

View File

@ -24,6 +24,7 @@ using Pixeval.Attributes;
namespace Pixeval.Options;
[LocalizationMetadata(typeof(MiscResources))]
public enum NovelDownloadFormat
{
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.Pdf))]

View File

@ -22,19 +22,45 @@ using Pixeval.Attributes;
namespace Pixeval.Options;
[LocalizationMetadata(typeof(ProxyTypeResources))]
public enum ProxyType
{
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ProxyOptionSystem))]
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionSystem))]
System,
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ProxyOptionNone))]
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionNone))]
None,
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionHttp))]
Http,
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionSocks4))]
Socks4,
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionSocks4A))]
Socks4A,
[LocalizedResource(typeof(MiscResources), nameof(ProxyTypeResources.ProxyOptionSocks5))]
Socks5
}
public static class ProxyTypeResources
{
public static string GetResource(string id) => id switch
{
nameof(ProxyType.Http) => ProxyOptionHttp,
nameof(ProxyType.Socks4) => ProxyOptionSocks4,
nameof(ProxyType.Socks4A) => ProxyOptionSocks4A,
nameof(ProxyType.Socks5) => ProxyOptionSocks5,
nameof(ProxyType.System) => ProxyOptionSystem,
_ => ProxyOptionNone
};
public const string ProxyOptionHttp = "http/https";
public const string ProxyOptionSocks4 = "socks4";
public const string ProxyOptionSocks4A = "socks4a";
public const string ProxyOptionSocks5 = "socks5";
public static string ProxyOptionNone => MiscResources.ProxyOptionSystem;
public static string ProxyOptionSystem => MiscResources.ProxyOptionNone;
}

View File

@ -1,4 +1,4 @@
#region Copyright (c) Pixeval/Pixeval
#region Copyright (c) Pixeval/Pixeval
// GPL v3 License
//
// Pixeval/Pixeval
@ -22,6 +22,7 @@ using Pixeval.Attributes;
namespace Pixeval.Options;
[LocalizationMetadata(typeof(MiscResources))]
public enum ThumbnailDirection
{
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ThumbnailDirectionLandscape))]
@ -29,4 +30,4 @@ public enum ThumbnailDirection
[LocalizedResource(typeof(MiscResources), nameof(MiscResources.ThumbnailDirectionPortrait))]
Portrait
}
}

Some files were not shown because too many files have changed in this diff Show More