[feat] Ctl framework

This commit is contained in:
acite
2025-10-05 03:00:26 +08:00
parent dcdd9d840e
commit af6dfbac8c
26 changed files with 578 additions and 79 deletions

4
.gitignore vendored
View File

@@ -57,4 +57,6 @@ nunit-*.xml
*.db
appsettings.json
appsettings.Development.json
appsettings.Development.json
build/
publish/

View File

@@ -0,0 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>abyssctl</w>
</words>
</dictionary>
</component>

View File

@@ -4,15 +4,36 @@
<projectFile profileName="http">Abyss/Abyss.csproj</projectFile>
<projectFile profileName="https">Abyss/Abyss.csproj</projectFile>
<projectFile>AbyssCli/AbyssCli.csproj</projectFile>
<projectFile>abyssctl/abyssctl.csproj</projectFile>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/Abyss/Components/Services/Admin/Attributes/Module.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Services/Admin/CtlService.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Services/Admin/Interfaces/IModule.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Services/Admin/Modules/HelloModule.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Services/Admin/Modules/VersionModule.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Components/Static/SocketExtensions.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/Abyss/Model/Admin/Ctl.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/abyssctl/App/App.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/abyssctl/App/Modules/HelloOptions.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/abyssctl/App/Modules/VersionOptions.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/abyssctl/Model/Ctl.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/abyssctl/Static/SocketExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss.sln" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss.sln" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss.sln.DotSettings.user" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss.sln.DotSettings.user" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Abyss.csproj" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Abyss.csproj" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/Security/UserService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/Security/UserService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Task/TaskController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Task/TaskController.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/Media/ComicService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/Media/ComicService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/Media/TaskService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/Media/TaskService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/Media/VideoService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/Media/VideoService.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/Abyss/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Program.cs" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -34,6 +55,7 @@
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/457530be4752476295767457c3639889d1a000/d0/3b166e9e/String.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/457530be4752476295767457c3639889d1a000/f3/fbf95091/SafeFileHandle.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/5df2accb46d040ccbbbe8331bf4d24b61daa00/df/93debd37/ControllerBase.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/61241bc83d094fe6ac4acdfe094b2b7f1e000/d9/09284666/ServiceProviderServiceExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/61fe11e9d86b4d2a9bd2b806929b7d381a400/a1/62750ee4/AsyncTableQuery`1.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/61fe11e9d86b4d2a9bd2b806929b7d381a400/e9/67f4a40e/SQLiteAsyncConnection.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/DecompilerCache/decompiler/7598e47d5cdf4107ba88f8220720fdc89000/a6/79d67871/xxHash128.cs" root0="FORCE_HIGHLIGHTING" />
@@ -45,6 +67,11 @@
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Security/RootController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Security/UserController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Controllers/Task/TaskController.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Admin/Attributes/Module.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Admin/CtlService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Admin/Interfaces/IModule.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Admin/Modules/HelloModule.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Admin/Modules/VersionModule.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Media/ComicService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Media/IndexService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Media/ResourceDatabaseService.cs" root0="FORCE_HIGHLIGHTING" />
@@ -55,10 +82,12 @@
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Security/AbyssService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Services/Security/UserService.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Static/Helpers.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Static/SocketExtensions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Tools/AbyssStream.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Tools/HttpHelper.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Components/Tools/HttpReader.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Misc/StringClusterer.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Admin/Ctl.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Media/Bookmark.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Media/Chip.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Media/Comic.cs" root0="FORCE_HIGHLIGHTING" />
@@ -71,7 +100,10 @@
<setting file="file://$PROJECT_DIR$/Abyss/Model/Security/ChallengeResponse.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Security/User.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/Abyss/Model/Security/UserCreating.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file:///usr/lib/dotnet/sdk/9.0.109/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.targets" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/abyssctl/App/App.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/abyssctl/App/Modules/HelloOptions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/abyssctl/App/Modules/VersionOptions.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$PROJECT_DIR$/abyssctl/Program.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
<component name="ProblemsViewState">
@@ -85,51 +117,44 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;.NET Launch Settings Profile.Abyss: http.executor&quot;: &quot;Run&quot;,
&quot;.NET Launch Settings Profile.Abyss: https.executor&quot;: &quot;Debug&quot;,
&quot;.NET Project.AbyssCli.executor&quot;: &quot;Run&quot;,
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;,
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;Publish to folder.Publish Abyss to folder x86.executor&quot;: &quot;Run&quot;,
&quot;Publish to folder.Publish Abyss to folder.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;XThreadsFramesViewSplitterKey&quot;: &quot;0.55813956&quot;,
&quot;git-widget-placeholder&quot;: &quot;main&quot;,
&quot;last_opened_file_path&quot;: &quot;/home/acite/AciteProjects/Abyss/README.md&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Launch Settings Profile.Abyss: http.executor": "Debug",
".NET Launch Settings Profile.Abyss: https.executor": "Debug",
".NET Project.AbyssCli.executor": "Run",
".NET Project.abyssctl.executor": "Run",
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"Publish to folder.Publish Abyss to folder x86.executor": "Run",
"Publish to folder.Publish Abyss to folder.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"XThreadsFramesViewSplitterKey": "0.55813956",
"git-widget-placeholder": "main",
"last_opened_file_path": "/home/acite/AciteProjects/Abyss/README.md",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"vue.rearranger.settings.migration": "true"
}
}</component>
<component name="RunManager" selected="Publish to folder.Publish Abyss to folder">
<configuration name="Publish Abyss to folder x86" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" platform="Any CPU" produce_single_file="true" ready_to_run="true" self_contained="true" target_folder="/opt/security/https/server" target_framework="net9.0" uuid_high="3690631506471504162" uuid_low="-4858628519588143325">
<runtimes>
<item value="linux-x64" />
</runtimes>
</riderPublish>
<method v="2" />
</configuration>
<configuration name="Publish Abyss to folder" type="DotNetFolderPublish" factoryName="Publish to folder">
<riderPublish configuration="Release" platform="Any CPU" produce_single_file="true" self_contained="true" target_folder="$PROJECT_DIR$/Abyss/bin/Release/net9.0/publish" target_framework="net9.0" uuid_high="3690631506471504162" uuid_low="-4858628519588143325">
}]]></component>
<component name="RunManager" selected=".NET Launch Settings Profile.Abyss: http">
<configuration name="Publish Abyss to folder" type="DotNetFolderPublish" factoryName="Publish to folder" singleton="false">
<riderPublish configuration="Release" platform="Any CPU" produce_single_file="true" self_contained="true" target_folder="$PROJECT_DIR$/publish" target_framework="net9.0" uuid_high="3690631506471504162" uuid_low="-4858628519588143325">
<runtimes>
<item value="linux-arm64" />
</runtimes>
</riderPublish>
<method v="2" />
</configuration>
<configuration name="AbyssCli" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="" />
<configuration name="abyssctl" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/build/net9.0/abyssctl" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/build/net9.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="ENV_FILE_PATHS" value="" />
@@ -139,12 +164,12 @@
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/AbyssCli/AbyssCli.csproj" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/abyssctl/abyssctl.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="" />
<option name="PROJECT_TFM" value="net9.0" />
<method v="2">
<option name="Build" />
</method>
@@ -167,9 +192,8 @@
</configuration>
<list>
<item itemvalue=".NET Launch Settings Profile.Abyss: http" />
<item itemvalue=".NET Project.AbyssCli" />
<item itemvalue=".NET Project.abyssctl" />
<item itemvalue="Publish to folder.Publish Abyss to folder" />
<item itemvalue="Publish to folder.Publish Abyss to folder x86" />
</list>
</component>
<component name="TaskManager">
@@ -254,6 +278,17 @@
<workItem from="1759314718830" duration="55000" />
<workItem from="1759315721112" duration="82000" />
<workItem from="1759398581423" duration="2195000" />
<workItem from="1759401971386" duration="69000" />
<workItem from="1759434890177" duration="183000" />
<workItem from="1759508787637" duration="115000" />
<workItem from="1759509008651" duration="2869000" />
<workItem from="1759515879741" duration="297000" />
<workItem from="1759516905127" duration="1451000" />
<workItem from="1759519618552" duration="9000" />
<workItem from="1759520741934" duration="642000" />
<workItem from="1759551752441" duration="5836000" />
<workItem from="1759561043616" duration="201000" />
<workItem from="1759591584659" duration="6926000" />
</task>
<servers />
</component>

View File

@@ -2,6 +2,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abyss", "Abyss\Abyss.csproj", "{3337C1CD-2419-4922-BC92-AF1A825DDF23}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "abyssctl", "abyssctl\abyssctl.csproj", "{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -12,5 +14,9 @@ Global
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3337C1CD-2419-4922-BC92-AF1A825DDF23}.Release|Any CPU.Build.0 = Release|Any CPU
{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F6DEF111-0CAD-4DCC-8957-7EBAFCF3D2C4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -7,6 +7,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKey_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ff09ccaeb94c34c2299acd3efee0facee1a400_003F81_003F137b58b4_003FKey_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMonitor_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F457530be4752476295767457c3639889d1a000_003F4c_003F4b962087_003FMonitor_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASafeFileHandle_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F457530be4752476295767457c3639889d1a000_003Ff3_003Ffbf95091_003FSafeFileHandle_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProviderServiceExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F61241bc83d094fe6ac4acdfe094b2b7f1e000_003Fd9_003F09284666_003FServiceProviderServiceExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AServiceProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F011a191356a243438f987de3ec3d6c6230800_003F04_003F8419ff35_003FServiceProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASQLiteAsyncConnection_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F61fe11e9d86b4d2a9bd2b806929b7d381a400_003Fe9_003F67f4a40e_003FSQLiteAsyncConnection_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AString_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F457530be4752476295767457c3639889d1a000_003Fd0_003F3b166e9e_003FString_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

View File

@@ -6,6 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>../build/</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="K4os.Hash.xxHash" Version="1.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8"/>

View File

@@ -14,7 +14,7 @@ namespace Abyss.Components.Controllers.Security;
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("Fixed")]
public class UserController(UserService userService, ILogger<UserController> logger) : BaseController
public class UserController(UserService userService) : BaseController
{
[HttpGet("{user}")]
public async Task<IActionResult> Challenge(string user)

View File

@@ -35,27 +35,26 @@ public class TaskController(ConfigureService config, TaskService taskService) :
return Ok(JsonConvert.SerializeObject(r, Formatting.Indented));
}
[HttpGet("{id}")]
public async Task<IActionResult> GetTask(string id)
{
throw new NotImplementedException();
}
[HttpPatch("{id}")]
public async Task<IActionResult> PutChip(string id)
{
throw new NotImplementedException();
}
[HttpPost("{id}")]
public async Task<IActionResult> VerifyChip(string id)
{
throw new NotImplementedException();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteTask(string id)
{
throw new NotImplementedException();
}
// [HttpGet("{id}")]
// public async Task<IActionResult> GetTask(string id)
// {
// throw new NotImplementedException();
// }
//
// [HttpPatch("{id}")]
// public async Task<IActionResult> PutChip(string id)
// {
// throw new NotImplementedException();
// }
//
// [HttpPost("{id}")]
// public async Task<IActionResult> VerifyChip(string id)
// {
// throw new NotImplementedException();
// }
// [HttpDelete("{id}")]
// public async Task<IActionResult> DeleteTask(string id)
// {
// throw new NotImplementedException();
// }
}

View File

@@ -0,0 +1,29 @@
using System.Reflection;
using Abyss.Components.Services.Admin.Interfaces;
namespace Abyss.Components.Services.Admin.Attributes;
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class Module(int head) : Attribute
{
public int Head { get; } = head;
public static Type[] Modules
{
get
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type attributeType = typeof(Module);
const string targetNamespace = "Abyss.Components.Services.Admin.Modules";
var moduleTypes = assembly.GetTypes()
.Where(t => t is { IsClass: true, IsAbstract: false, IsInterface: false })
.Where(t => t.Namespace == targetNamespace)
.Where(t => typeof(IModule).IsAssignableFrom(t))
.Where(t => t.IsDefined(attributeType, inherit: false))
.ToArray();
return moduleTypes;
}
}
}

View File

@@ -0,0 +1,110 @@
using System.Net.Sockets;
using System.Text;
using Abyss.Components.Static;
using Abyss.Model.Admin;
using Newtonsoft.Json;
using System.Reflection;
using Abyss.Components.Services.Admin.Interfaces;
using Module = Abyss.Components.Services.Admin.Attributes.Module;
namespace Abyss.Components.Services.Admin;
public class CtlService(ILogger<CtlService> logger, IServiceProvider serviceProvider) : IHostedService
{
private readonly string _socketPath = "ctl.sock";
private Task? _executingTask;
private CancellationTokenSource? _cts;
private Dictionary<int, Type> _handlers = new();
public Task StartAsync(CancellationToken cancellationToken)
{
var t = Module.Modules;
foreach (var module in t)
{
var attr = module.GetCustomAttribute<Module>();
if (attr != null)
{
_handlers[attr.Head] = module;
}
}
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_executingTask = ExecuteAsync(_cts.Token);
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
return;
try
{
_cts?.CancelAsync();
}
finally
{
await Task.WhenAny(_executingTask,
Task.Delay(Timeout.Infinite, cancellationToken));
}
}
private async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (File.Exists(_socketPath))
{
File.Delete(_socketPath);
}
var endPoint = new UnixDomainSocketEndPoint(_socketPath);
using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
socket.Bind(endPoint);
socket.Listen(5);
while (!stoppingToken.IsCancellationRequested)
{
try
{
var clientSocket = await socket.AcceptAsync(stoppingToken);
_ = HandleClientAsync(clientSocket, stoppingToken);
}
catch (OperationCanceledException)
{
break;
}
}
}
private async Task HandleClientAsync(Socket clientSocket, CancellationToken stoppingToken)
{
async Task _400()
{
await clientSocket.WriteBase64Async(Ctl.MakeBase64(400, ["Bad Request"]), stoppingToken);
}
try
{
var s = Encoding.UTF8.GetString(
Convert.FromBase64String(await clientSocket.ReadBase64Async(stoppingToken)));
var json = JsonConvert.DeserializeObject<Ctl>(s);
if (json == null || !_handlers.TryGetValue(json.Head, out var handler))
{
await _400();
return;
}
var module = (serviceProvider.GetRequiredService(handler) as IModule)!;
var r = await module.ExecuteAsync(json, stoppingToken);
await clientSocket.WriteBase64Async(Ctl.MakeBase64(r.Head, r.Params), stoppingToken);
}
catch (Exception e)
{
logger.LogError(e, "Error while handling client connection");
}
}
}

View File

@@ -0,0 +1,8 @@
using Abyss.Model.Admin;
namespace Abyss.Components.Services.Admin.Interfaces;
public interface IModule
{
public Task<Ctl> ExecuteAsync(Ctl request, CancellationToken ct);
}

View File

@@ -0,0 +1,18 @@
using Abyss.Components.Services.Admin.Attributes;
using Abyss.Components.Services.Admin.Interfaces;
using Abyss.Model.Admin;
namespace Abyss.Components.Services.Admin.Modules;
[Module(100)]
public class HelloModule: IModule
{
public async Task<Ctl> ExecuteAsync(Ctl request, CancellationToken ct)
{
return await Task.FromResult(new Ctl
{
Head = 200,
Params = ["Hi"],
});
}
}

View File

@@ -0,0 +1,14 @@
using Abyss.Components.Services.Admin.Attributes;
using Abyss.Components.Services.Admin.Interfaces;
using Abyss.Model.Admin;
namespace Abyss.Components.Services.Admin.Modules;
[Module(101)]
public class VersionModule: IModule
{
public async Task<Ctl> ExecuteAsync(Ctl request, CancellationToken ct)
{
throw new NotImplementedException();
}
}

View File

@@ -3,11 +3,10 @@ using Abyss.Components.Static;
using Abyss.Model.Media;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Task = System.Threading.Tasks.Task;
namespace Abyss.Components.Services.Media;
public class ComicService(ILogger<ComicService> logger, ResourceService rs, ConfigureService config)
public class ComicService(ResourceService rs, ConfigureService config)
{
public readonly string ImageFolder = Path.Combine(config.MediaRoot, "Images");

View File

@@ -10,7 +10,7 @@ namespace Abyss.Components.Services.Media;
public class TaskService(ILogger<TaskService> logger, ConfigureService config, ResourceService rs, UserService user)
public class TaskService(ConfigureService config, ResourceService rs, UserService user)
{
public readonly string TaskFolder = Path.Combine(config.MediaRoot, "Tasks");
public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");
@@ -26,7 +26,7 @@ public class TaskService(ILogger<TaskService> logger, ConfigureService config, R
foreach (var i in r ?? [])
{
var p = Helpers.SafePathCombine(TaskFolder, [i, "task.json"]);
var c = JsonConvert.DeserializeObject<Task>(await System.IO.File.ReadAllTextAsync(p ?? ""));
var c = JsonConvert.DeserializeObject<Task>(await File.ReadAllTextAsync(p ?? ""));
if(c?.Owner == u) s.Add(i);
}
@@ -50,7 +50,8 @@ public class TaskService(ILogger<TaskService> logger, ConfigureService config, R
switch ((TaskType)creation.Type)
{
case TaskType.Image:
return await CreateImageTask(token, ip, creation);
throw new NotImplementedException();
// return await CreateImageTask(token, ip, creation);
case TaskType.Video:
return await CreateVideoTask(token, ip, creation);
default:
@@ -105,10 +106,10 @@ public class TaskService(ILogger<TaskService> logger, ConfigureService config, R
return r;
}
private async Task<TaskCreationResponse?> CreateImageTask(string token, string ip, TaskCreation creation)
{
throw new NotImplementedException();
}
// private async Task<TaskCreationResponse?> CreateImageTask(string token, string ip, TaskCreation creation)
// {
// throw new NotImplementedException();
// }
public static uint GenerateUniqueId(string parentDirectory)
{

View File

@@ -7,7 +7,7 @@ using Newtonsoft.Json;
namespace Abyss.Components.Services.Media;
public class VideoService(ILogger<VideoService> logger, ResourceService rs, ConfigureService config)
public class VideoService(ResourceService rs, ConfigureService config)
{
public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");

View File

@@ -0,0 +1,51 @@
using System.Net.Sockets;
using System.Text;
namespace Abyss.Components.Static;
public static class SocketExtensions
{
public static async Task<string> ReadBase64Async(this Socket socket, CancellationToken cancellationToken = default)
{
var buffer = new byte[4096];
var sb = new StringBuilder();
while (true)
{
int bytesRead = await socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
throw new SocketException((int)SocketError.ConnectionReset);
string chunk = Encoding.UTF8.GetString(buffer, 0, bytesRead);
sb.Append(chunk);
int newlineIndex = sb.ToString().IndexOf('\n');
if (newlineIndex >= 0)
{
string base64 = sb.ToString(0, newlineIndex).Trim();
sb.Remove(0, newlineIndex + 1);
return base64;
}
}
}
public static async Task WriteBase64Async(this Socket socket, string base64, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(base64))
throw new ArgumentException("Base64 string cannot be null or empty.", nameof(base64));
string message = base64 + "\n";
byte[] data = Encoding.UTF8.GetBytes(message);
int totalSent = 0;
while (totalSent < data.Length)
{
int sent = await socket.SendAsync(data.AsMemory(totalSent), SocketFlags.None, cancellationToken).ConfigureAwait(false);
if (sent == 0)
throw new SocketException((int)SocketError.ConnectionReset);
totalSent += sent;
}
}
}

19
Abyss/Model/Admin/Ctl.cs Normal file
View File

@@ -0,0 +1,19 @@
using System.Text;
using Newtonsoft.Json;
namespace Abyss.Model.Admin;
public class Ctl
{
[JsonProperty("head")]
public int Head { get; set; }
[JsonProperty("params")] public string[] Params { get; set; } = [];
public static string MakeBase64(int head, string[] param)
{
return Convert.ToBase64String(
Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new Ctl
{ Head = head, Params = param })));
}
}

View File

@@ -2,7 +2,9 @@
using System.Threading.RateLimiting;
using Abyss.Components.Controllers.Middleware;
using Abyss.Components.Controllers.Task;
using Abyss.Components.Services.Admin;
using Abyss.Components.Services.Admin.Attributes;
using Abyss.Components.Services.Admin.Modules;
using Abyss.Components.Services.Media;
using Abyss.Components.Services.Misc;
using Abyss.Components.Services.Security;
@@ -30,6 +32,12 @@ public class Program
builder.Services.AddSingleton<VideoService>();
builder.Services.AddSingleton<ComicService>();
builder.Services.AddHostedService<AbyssService>();
builder.Services.AddHostedService<CtlService>();
foreach (var t in Module.Modules)
{
builder.Services.AddTransient(t);
}
builder.Services.AddRateLimiter(options =>
{

49
abyssctl/App/App.cs Normal file
View File

@@ -0,0 +1,49 @@
using System.Net.Sockets;
using System.Text;
using abyssctl.App.Modules;
using abyssctl.Model;
using abyssctl.Static;
using CommandLine;
using Newtonsoft.Json;
namespace abyssctl.App;
public class App
{
private static readonly string SocketPath = "ctl.sock";
public static async Task<Ctl> CtlWriteRead(Ctl ctl)
{
var endPoint = new UnixDomainSocketEndPoint(SocketPath);
using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
try
{
await socket.ConnectAsync(endPoint);
await socket.WriteBase64Async(Ctl.MakeBase64(ctl.Head, ctl.Params));
var s = Encoding.UTF8.GetString(
Convert.FromBase64String(await socket.ReadBase64Async()));
return JsonConvert.DeserializeObject<Ctl>(s)!;
}
catch (Exception e)
{
return new Ctl
{
Head = 500,
Params = [e.Message]
};
}
}
public async Task<int> RunAsync(string[] args)
{
return await Task.Run(() =>
{
return Parser.Default.ParseArguments<HelloOptions, VersionOptions>(args)
.MapResult(
(HelloOptions opt) => HelloOptions.Run(opt),
(VersionOptions opt) => VersionOptions.Run(opt),
_ => 1);
});
}
}

View File

@@ -0,0 +1,20 @@
using abyssctl.Model;
using CommandLine;
namespace abyssctl.App.Modules;
[Verb("hello", HelpText = "Say hello to abyss server")]
public class HelloOptions
{
public static int Run(HelloOptions opts)
{
var r = App.CtlWriteRead(new Ctl
{
Head = 100,
Params = []
}).GetAwaiter().GetResult();
Console.WriteLine($"Response Code: {r.Head}");
Console.WriteLine($"Params: {string.Join(",", r.Params)}");
return 0;
}
}

View File

@@ -0,0 +1,13 @@
using CommandLine;
namespace abyssctl.App.Modules;
[Verb("ver", HelpText = "Get server version")]
public class VersionOptions
{
public static int Run(VersionOptions opts)
{
Console.WriteLine("Version");
return 0;
}
}

19
abyssctl/Model/Ctl.cs Normal file
View File

@@ -0,0 +1,19 @@
using System.Text;
using Newtonsoft.Json;
namespace abyssctl.Model;
public class Ctl
{
[JsonProperty("head")] public int Head { get; set; }
[JsonProperty("params")] public string[] Params { get; set; } = [];
public static string MakeBase64(int head, string[] param)
{
return Convert.ToBase64String(
Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(new Ctl
{ Head = head, Params = param })));
}
}

13
abyssctl/Program.cs Normal file
View File

@@ -0,0 +1,13 @@

namespace abyssctl;
static class Program
{
static async Task<int> Main(string[] args)
{
var app = new App.App();
return await app.RunAsync(args);
}
}

View File

@@ -0,0 +1,50 @@
using System.Net.Sockets;
using System.Text;
namespace abyssctl.Static;
public static class SocketExtensions
{
public static async Task<string> ReadBase64Async(this Socket socket, CancellationToken cancellationToken = default)
{
var buffer = new byte[4096];
var sb = new StringBuilder();
while (true)
{
int bytesRead = await socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
throw new SocketException((int)SocketError.ConnectionReset);
string chunk = Encoding.UTF8.GetString(buffer, 0, bytesRead);
sb.Append(chunk);
int newlineIndex = sb.ToString().IndexOf('\n');
if (newlineIndex >= 0)
{
string base64 = sb.ToString(0, newlineIndex).Trim();
sb.Remove(0, newlineIndex + 1);
return base64;
}
}
}
public static async Task WriteBase64Async(this Socket socket, string base64, CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(base64))
throw new ArgumentException("Base64 string cannot be null or empty.", nameof(base64));
string message = base64 + "\n";
byte[] data = Encoding.UTF8.GetBytes(message);
int totalSent = 0;
while (totalSent < data.Length)
{
int sent = await socket.SendAsync(data.AsMemory(totalSent), SocketFlags.None, cancellationToken).ConfigureAwait(false);
if (sent == 0)
throw new SocketException((int)SocketError.ConnectionReset);
totalSent += sent;
}
}
}

24
abyssctl/abyssctl.csproj Normal file
View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>false</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<OutputPath>../build/</OutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="9.0.9" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="System.CommandLine" Version="2.0.0-rc.1.25451.107" />
</ItemGroup>
</Project>