Remove native (#2700)

* Remove native target

* Add foojay-resolver-convention

* disable windows
This commit is contained in:
Him188 2023-06-16 13:40:46 +01:00 committed by GitHub
parent 9a6b9cc900
commit 8ff64d4a7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
152 changed files with 55 additions and 9465 deletions

View File

@ -1,88 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
val env = "\${{ env.gradleArgs }}"
val isUbunutu = "\${{ env.isUbuntu == 'true' }}"
val isWindows = "\${{ env.isWindows == 'true' }}"
val isMac = "\${{ env.isMac == 'true' }}"
val template = """
- if: CONDITION
name: "Compile mirai-core-api for macosArm64"
run: ./gradlew :mirai-core-api:compileKotlinMacosArm64 :mirai-core-api:compileTestKotlinMacosArm64 $env
- if: CONDITION
name: "Link mirai-core-api for macosArm64"
run: ./gradlew mirai-core-api:linkDebugTestMacosArm64 $env
- if: CONDITION
name: "Test mirai-core-api for macosArm64"
run: ./gradlew :mirai-core-api:macosArm64Test $env
""".trimIndent()
val output = buildString {
val title = "############# GENERATED FROM generate-build-native.ws.kts #############"
appendLine("#".repeat(title.length))
appendLine(title)
appendLine("#".repeat(title.length))
appendLine()
listOf("mirai-core-utils", "mirai-core-api", "mirai-core").forEach { moduleName ->
appendLine(
"""
- name: "Commonize mirai-core-api"
run: ./gradlew :mirai-core-api:commonize $env
""".trimIndent().replace("mirai-core-api", moduleName)
)
appendLine()
}
listOf("mirai-core-utils", "mirai-core-api", "mirai-core").forEach { moduleName ->
appendLine("# $moduleName")
appendLine()
appendLine(
"""
- name: "Compile mirai-core-api for common"
run: ./gradlew :mirai-core-api:compileCommonMainKotlinMetadata $env
- name: "Compile mirai-core-api for native"
run: ./gradlew :mirai-core-api:compileNativeMainKotlinMetadata $env
- name: "Compile mirai-core-api for unix-like"
run: ./gradlew :mirai-core-api:compileUnixMainKotlinMetadata $env
""".trimIndent().replace("mirai-core-api", moduleName)
)
appendLine()
listOf("macosX64" to isMac, "mingwX64" to isWindows, "linuxX64" to isUbunutu).forEach { (target, condition) ->
appendLine(useTemplate(moduleName, target, condition))
appendLine()
appendLine()
}
appendLine()
}
this.trimEnd().let { c -> clear().appendLine(c) } // remove trailing empty lines
appendLine()
appendLine("#".repeat(title.length))
}
println(output.prependIndent(" ".repeat(6)))
fun useTemplate(moduleName: String, target: String, condition: String) = template
.replace("mirai-core-api", moduleName)
.replace("macosArm64", target)
.replace("MacosArm64", target.replaceFirstChar { it.uppercaseChar() })
.replace("CONDITION", condition)
// Link release artifacts to save memory
.replace("linkDebugTestMingwX64", "linkReleaseTestMingwX64")

View File

@ -14,8 +14,8 @@ on:
- '**/*.md'
jobs:
build-mirai-jvm:
name: "JVM (${{ matrix.os }})"
build:
name: "Build (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
@ -24,7 +24,7 @@ jobs:
# - windows-2022
- macos-12
env:
gradleArgs: --scan "-Dmirai.target=jvm;android;!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx6000m" "-Dfile.encoding=UTF-8"
gradleArgs: --scan
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
@ -77,6 +77,24 @@ jobs:
- name: "Check"
run: ./gradlew check ${{ env.gradleArgs }}
# Snapshots
- if: ${{ env.isMac == 'true' }}
name: Ensure KDoc valid
run: ./gradlew dokkaHtmlMultiModule ${{ env.gradleArgs }}
- name: Release RAM
run: node ci-release-helper/scripts/kill-java.js
- name: Publish Snapshots
if: ${{ github.event.pusher && env.isMac == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }}
run: ./gradlew publishAllPublicationsToMiraiRepoRepository ${{ env.gradleArgs }}
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
# Upload
- name: Upload mirai-core-utils
@ -156,249 +174,3 @@ jobs:
with:
name: mirai-logging-slf4j-simple
path: logging/mirai-logging-slf4j-simple/build/libs
build-mirai-all:
name: "Everything (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- macos-12
env:
enableLocalPublishingTest: 'false'
gradleArgs: --scan
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }}
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Cache konan
uses: pat-s/always-upload-cache@v3.0.11
with:
path: ~/.konan
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- if: ${{ env.isUnix == 'true' }}
run: chmod -R 777 *
- name: Clean and download dependencies
run: ./gradlew clean ${{ env.gradleArgs }}
- run: >
./gradlew updateSnapshotVersion ${{ env.gradleArgs }}
if: github.event.pusher && vars.RUN_MIRAI_SNAPSHOTS == 'true'
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
MIRAI_BUILD_INDEX_AUTH_USERNAME: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_USERNAME }}
MIRAI_BUILD_INDEX_AUTH_PASSWORD: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_PASSWORD }}
- name: "Assemble"
run: ./gradlew assemble ${{ env.gradleArgs }}
- name: Publish Local Artifacts
if: ${{ env.enableLocalPublishingTest == 'true' }}
run: ./gradlew :mirai-deps-test:publishMiraiArtifactsToMavenLocal ${{ env.gradleArgs }} "-Dmirai.build.project.version=2.99.0-deps-test"
- name: "Check"
run: ./gradlew check ${{ env.gradleArgs }}
- if: ${{ env.isMac == 'true' }}
name: Ensure KDoc valid
run: ./gradlew dokkaHtmlMultiModule ${{ env.gradleArgs }}
- name: Release RAM
run: node ci-release-helper/scripts/kill-java.js
- name: Publish Snapshots
if: ${{ github.event.pusher && env.isMac == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }}
run: ./gradlew publishAllPublicationsToMiraiRepoRepository ${{ env.gradleArgs }}
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
build-mirai-core-native:
name: "Native (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- windows-2022
- ubuntu-20.04
# - ubuntu-18.04
# - macos-12
# - macos-11
include:
- os: windows-2022
targetName: mingwX64
- os: ubuntu-20.04
targetName: linuxX64
# - os: macos-12
# targetName: macosX64
# - os: macos-11
# targetName: macosX64
env:
# FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation`
# > Declaration annotated with '@OptionalExpectation' can only be used in common module sources
enableLocalPublishingTest: 'false'
gradleArgs: --scan "-Dmirai.target=jvm;${{ matrix.targetName }};!other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8"
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }}
VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\vcpkg\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- uses: actions/setup-java@v3
with:
distribution: 'adopt-openj9'
java-version: '17'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Cache konan
uses: pat-s/always-upload-cache@v3.0.11
with:
path: ~/.konan
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Prepare to cache vcpkg
if: ${{ env.isWindows == 'true' }}
run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
- name: Cache vcpkg
if: ${{ env.isWindows == 'true' }}
uses: pat-s/always-upload-cache@v3.0.11
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: ${{ runner.os }}-vcpkg-binary-cache-${{ github.job }}
restore-keys: |
${{ runner.os }}-vcpkg-binary-cache-
- if: ${{ env.isUnix == 'true' }}
run: chmod -R 777 *
# Prepare environment for linking on macOS
- if: ${{ env.isMac == 'true' }}
name: Install OpenSSL on Mac OS
run: >
git clone https://github.com/openssl/openssl.git --recursive &&
cd openssl &&
git checkout tags/openssl-3.0.3 &&
./Configure --prefix=/opt/openssl --openssldir=/usr/local/ssl &&
make &&
sudo make install
# Prepare environment for linking on Ubuntu
- if: ${{ env.isUbuntu == 'true' }}
name: Install OpenSSL on Ubuntu
run: sudo apt install libssl-dev -y
# Prepare environment for linking on Windows
- if: ${{ env.isWindows == 'true' }}
name: Setup Memory Environment on Windows
run: >
wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=9216 &
net stop mongodb
shell: cmd
continue-on-error: true
- if: ${{ env.isWindows == 'true' }}
name: Install OpenSSL & cURL on Windows
run: |
echo "set(VCPKG_BUILD_TYPE release)" | Out-File -FilePath "$env:VCPKG_INSTALLATION_ROOT\triplets\x64-windows.cmake" -Encoding utf8 -Append
vcpkg install openssl:x64-windows curl[core,ssl]:x64-windows
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\crypto.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcrypto.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\ssl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libssl.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\curl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcurl.lib
echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Clean and download dependencies
run: ./gradlew clean ${{ env.gradleArgs }}
- run: >
./gradlew updateSnapshotVersion ${{ env.gradleArgs }}
if: github.event.pusher && vars.RUN_MIRAI_SNAPSHOTS == 'true'
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
MIRAI_BUILD_INDEX_AUTH_USERNAME: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_USERNAME }}
MIRAI_BUILD_INDEX_AUTH_PASSWORD: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_PASSWORD }}
- name: "Test mirai-core-utils for ${{ matrix.os }}"
run: ./gradlew :mirai-core-utils:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core-api for ${{ matrix.os }}"
run: ./gradlew :mirai-core-api:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core for ${{ matrix.os }}"
run: ./gradlew :mirai-core:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: Publish Local Artifacts
if: ${{ env.enableLocalPublishingTest == 'true' }}
run: ./gradlew :mirai-deps-test:publishMiraiArtifactsToMavenLocal ${{ env.gradleArgs }} "-Dmirai.build.project.version=2.99.0-deps-test"
- name: Check Publication
if: ${{ env.enableLocalPublishingTest == 'true' }}
run: ./gradlew :mirai-deps-test:check ${{ env.gradleArgs }}
# Publish native snapshots. Other artifacts are published in build-mirai-all
- name: Release RAM
run: node ci-release-helper/scripts/kill-java.js
- name: Publish MingwX64 Snapshots
if: ${{ github.event.pusher && env.isWindows == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }}
run: ./gradlew publishMingwX64PublicationToMiraiRepoRepository ${{ env.gradleArgs }}
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
- name: Publish LinuxX64 Snapshots
if: ${{ github.event.pusher && env.isUbuntu == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }}
run: ./gradlew publishLinuxX64PublicationToMiraiRepoRepository ${{ env.gradleArgs }}
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}
- name: Publish macOSX64 Snapshots
if: ${{ github.event.pusher && env.isMac == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }}
run: ./gradlew publishMacosX64PublicationToMiraiRepoRepository ${{ env.gradleArgs }}
env:
MIRAI_IS_SNAPSHOTS_PUBLISHING: true
SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }}
SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }}
SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }}

View File

@ -25,7 +25,7 @@ jobs:
fail-fast: false
matrix:
os:
# - windows-2022
# - windows-2022 # OOM
- ubuntu-20.04
- macos-12
include:
@ -41,7 +41,7 @@ jobs:
env:
# FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation`
# > Declaration annotated with '@OptionalExpectation' can only be used in common module sources
gradleArgs: --scan "-Dmirai.target=jvm;${{ matrix.targetName }};~others" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m" "-Dfile.encoding=UTF-8"
gradleArgs: --scan
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}

View File

@ -102,16 +102,6 @@ jobs:
# Prepare environment for linking for macOS
- if: ${{ env.isMac == 'true' }}
name: Install OpenSSL
run: >
git clone https://github.com/openssl/openssl.git --recursive &&
cd openssl &&
git checkout tags/openssl-3.0.3 &&
./Configure --prefix=/opt/openssl --openssldir=/usr/local/ssl &&
make &&
sudo make install
- name: Clean and download dependencies
run: ./gradlew clean ${{ env.gradleArgs }}
@ -155,164 +145,6 @@ jobs:
-Dgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }} -Pgradle.publish.secret=${{ secrets.GRADLE_PUBLISH_SECRET }}
continue-on-error: true
publish-core-native:
name: "Native (${{ matrix.os }})"
needs: [ publish-others ] # Allow MPP metadata to be uploaded first.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- windows-2022
- ubuntu-20.04
# - macos-12 # macOS artifacts published in 'publish-others'
include:
- os: windows-2022
targetName: mingwX64
# parallelCompilation: false
- os: ubuntu-20.04
targetName: linuxX64
# parallelCompilation: false
- os: macos-12
targetName: macosX64
# parallelCompilation: true # macOS machine has 14G
env:
# FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation`
# > Declaration annotated with '@OptionalExpectation' can only be used in common module sources
gradleArgs: --scan "-Dmirai.target=jvm;${{ matrix.targetName }};~other" "-Pkotlin.compiler.execution.strategy=in-process" "-Dorg.gradle.jvmargs=-Xmx4096m" "-Dfile.encoding=UTF-8"
isMac: ${{ startsWith(matrix.os, 'macos') }}
isWindows: ${{ startsWith(matrix.os, 'windows') }}
isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }}
isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }}
VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\vcpkg\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }}
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- uses: actions/setup-java@v3
with:
distribution: 'adopt-openj9'
java-version: '17'
- name: Keys setup
shell: bash
run: |
mkdir build-gpg-sign
echo "$GPG_PRIVATE" > build-gpg-sign/keys.gpg
echo "$GPG_PUBLIC_" > build-gpg-sign/keys.gpg.pub
env:
GPG_PRIVATE: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PUBLIC_: ${{ secrets.GPG_PUBLIC_KEY }}
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Cache konan
uses: pat-s/always-upload-cache@v3.0.11
with:
path: ~/.konan
key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Prepare to cache vcpkg
if: ${{ env.isWindows == 'true' }}
run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
- name: Cache vcpkg
if: ${{ env.isWindows == 'true' }}
uses: pat-s/always-upload-cache@v3.0.11
with:
path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
key: ${{ runner.os }}-vcpkg-binary-cache-${{ github.job }}
restore-keys: |
${{ runner.os }}-vcpkg-binary-cache-
- if: ${{ env.isUnix == 'true' }}
run: chmod -R 777 *
# Prepare environment for linking on macOS
- if: ${{ env.isMac == 'true' }}
name: Install OpenSSL on Mac OS
run: >
git clone https://github.com/openssl/openssl.git --recursive &&
cd openssl &&
git checkout tags/openssl-3.0.3 &&
./Configure --prefix=/opt/openssl --openssldir=/usr/local/ssl &&
make &&
sudo make install
# Prepare environment for linking on Ubuntu
- if: ${{ env.isUbuntu == 'true' }}
name: Install OpenSSL on Ubuntu
run: sudo apt install libssl-dev -y
# Prepare environment for linking on Windows
- if: ${{ env.isWindows == 'true' }}
name: Setup Memory Environment on Windows
run: >
wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=9216 &
net stop mongodb
shell: cmd
continue-on-error: true
- if: ${{ env.isWindows == 'true' }}
name: Install OpenSSL & cURL on Windows
run: |
echo "set(VCPKG_BUILD_TYPE release)" | Out-File -FilePath "$env:VCPKG_INSTALLATION_ROOT\triplets\x64-windows.cmake" -Encoding utf8 -Append
vcpkg install openssl:x64-windows curl[core,ssl]:x64-windows
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\crypto.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcrypto.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\ssl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libssl.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\curl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcurl.lib
echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Clean and download dependencies
run: ./gradlew clean ${{ env.gradleArgs }}
- name: "Test mirai-core-utils for ${{ matrix.os }}"
run: ./gradlew :mirai-core-utils:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core-api for ${{ matrix.os }}"
run: ./gradlew :mirai-core-api:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: "Test mirai-core for ${{ matrix.os }}"
run: ./gradlew :mirai-core:${{ matrix.targetName }}Test ${{ env.gradleArgs }}
- name: Initialize Publishing Caching Repository
run: ./gradlew runcihelper --args sync-maven-metadata ${{ env.gradleArgs }}
- name: Release RAM
run: node ci-release-helper/scripts/kill-java.js
# # Parallel compilation will exhaust machine memory causing OOM
# - name: Assemble
# run: ./gradlew assemble ${{ env.gradleArgs }} "-Porg.gradle.parallel=${{ matrix.parallelCompilation }}"
- name: Publish MingwX64
if: ${{ env.isWindows == 'true' }}
run: ./gradlew publishMingwX64PublicationToMiraiStageRepoRepository ${{ env.gradleArgs }}
- name: Publish LinuxX64
if: ${{ env.isUbuntu == 'true' }}
run: ./gradlew publishLinuxX64PublicationToMiraiStageRepoRepository ${{ env.gradleArgs }}
- name: Publish macOSX64
if: ${{ env.isMac == 'true' }}
run: ./gradlew publishMacosX64PublicationToMiraiStageRepoRepository ${{ env.gradleArgs }}
- name: Restore staging repository id
uses: actions/download-artifact@v3
with:
name: publish-stage-id
path: ci-release-helper/repoid
- name: Release RAM
run: node ci-release-helper/scripts/kill-java.js
- name: Publish to maven central
run: ./gradlew runcihelper --args publish-to-maven-central --scan "-Pcihelper.cert.username=${{ secrets.SONATYPE_USER }}" "-Pcihelper.cert.password=${{ secrets.SONATYPE_KEY }}"
#
# close-repository:
# runs-on: macos-12

View File

@ -1,33 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build mirai-core for host Native" type="GradleRunConfiguration"
factoryName="Gradle" folderName="Build mirai-core">
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:linkDebugTestHost"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,34 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build mirai-core libraries for host Native" type="GradleRunConfiguration"
factoryName="Gradle" folderName="Build mirai-core">
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:linkReleaseSharedHost"/>
<option value=":mirai-core:linkReleaseStaticHost"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,40 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Native Main host" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Native Main">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.native.test.main" value="true"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$/mirai-core"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:hostTest"/>
<option value="--tests"/>
<option value="&quot;net.mamoe.mirai.internal.test.NativeTestWrapper.test&quot;"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,40 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Native Main linuxX64" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Native Main">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.native.test.main" value="true"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$/mirai-core"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:linuxX64Test"/>
<option value="--tests"/>
<option value="&quot;net.mamoe.mirai.internal.test.NativeTestWrapper.test&quot;"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,40 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Native Main macosArm64" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Native Main">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.native.test.main" value="true"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$/mirai-core"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:macosArm64Test"/>
<option value="--tests"/>
<option value="&quot;net.mamoe.mirai.internal.test.NativeTestWrapper.test&quot;"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,40 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Native Main macosX64" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Native Main">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.native.test.main" value="true"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$/mirai-core"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:macosX64Test"/>
<option value="--tests"/>
<option value="&quot;net.mamoe.mirai.internal.test.NativeTestWrapper.test&quot;"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,40 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Native Main mingwX64" type="GradleRunConfiguration" factoryName="Gradle"
folderName="Native Main">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="mirai.native.test.main" value="true"/>
</map>
</option>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$/mirai-core"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:mingwX64Test"/>
<option value="--tests"/>
<option value="&quot;net.mamoe.mirai.internal.test.NativeTestWrapper.test&quot;"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,33 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test mirai-core for host Native" type="GradleRunConfiguration"
factoryName="Gradle" folderName="Build mirai-core">
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core:hostTest"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,33 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test mirai-core-api for host Native" type="GradleRunConfiguration"
factoryName="Gradle" folderName="Build mirai-core">
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core-api:hostTest"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -1,33 +0,0 @@
<!--
~ Copyright 2019-2022 Mamoe Technologies and contributors.
~
~ 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
~ Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
~
~ https://github.com/mamoe/mirai/blob/dev/LICENSE
-->
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Test mirai-core-utils for host Native" type="GradleRunConfiguration"
factoryName="Gradle" folderName="Build mirai-core">
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value=":mirai-core-api:hostTest"/>
</list>
</option>
<option name="vmOptions"/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

View File

@ -10,21 +10,10 @@
import com.google.gradle.osdetector.OsDetector
import org.gradle.api.Project
import org.gradle.api.attributes.Attribute
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getting
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.TEST_COMPILATION_NAME
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset
import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink
import java.io.File
import java.util.*
val MIRAI_PLATFORM_ATTRIBUTE: Attribute<String> = Attribute.of(
"net.mamoe.mirai.platform", String::class.java
@ -78,10 +67,6 @@ val HOST_KIND by lazy {
}
}
enum class HostArch {
X86, X64, ARM32, ARM64
}
/// eg. "!a;!b" means to enable all targets but a or b
/// eg. "a;b;!other" means to disable all targets but a or b
val ENABLED_TARGETS by projectLazy {
@ -98,16 +83,11 @@ fun getMiraiTargetFromGradle() =
?: "others"
fun isTargetEnabled(name: String): Boolean {
val isNative = name in POSSIBLE_NATIVE_TARGETS
return when {
name in ENABLED_TARGETS -> true // explicitly enabled
"!$name" in ENABLED_TARGETS -> false // explicitly disabled
"~$name" in ENABLED_TARGETS -> false // explicitly disabled
"native" in ENABLED_TARGETS && isNative -> true // native targets explicitly enabled
"!native" in ENABLED_TARGETS && isNative -> false // native targets explicitly disabled
"~native" in ENABLED_TARGETS && isNative -> false // native targets explicitly disabled
"!other" in ENABLED_TARGETS -> false // others disabled
"~other" in ENABLED_TARGETS -> false // others disabled
"!others" in ENABLED_TARGETS -> false // others disabled
@ -119,41 +99,6 @@ fun isTargetEnabled(name: String): Boolean {
fun Set<String>.filterTargets() =
this.filter { isTargetEnabled(it) }.toSet()
val MAC_TARGETS: Set<String> by projectLazy {
setOf(
// "watchosX86",
"macosX64",
"macosArm64",
// Failed to generate cinterop for :mirai-core:cinteropOpenSSLIosX64: Process 'command '/Library/Java/JavaVirtualMachines/temurin-17.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1
// Exception in thread "main" java.lang.Error: /var/folders/kw/ndw272ns06s7cys2mcwwlb5c0000gn/T/1181140105365175708.c:1:10: fatal error: 'openssl/ec.h' file not found
//
// Note: the openssl/ec.h is actually there, seems Kotlin doesn't support that
// "iosX64",
// "iosArm64",
// "iosArm32",
// "iosSimulatorArm64",
// "watchosX64",
// "watchosArm32",
// "watchosArm64",
// "watchosSimulatorArm64",
// "tvosX64",
// "tvosArm64",
// "tvosSimulatorArm64",
).filterTargets()
}
val WIN_TARGETS by projectLazy { setOf("mingwX64").filterTargets() }
val LINUX_TARGETS by projectLazy { setOf("linuxX64").filterTargets() }
val UNIX_LIKE_TARGETS by projectLazy { LINUX_TARGETS + MAC_TARGETS }
val NATIVE_TARGETS by projectLazy { UNIX_LIKE_TARGETS + WIN_TARGETS }
private val POSSIBLE_NATIVE_TARGETS by lazy { setOf("mingwX64", "macosX64", "macosArm64", "linuxX64") }
const val JVM_TOOLCHAIN_VERSION = 8
val JVM_TOOLCHAIN_ENABLED by projectLazy {
@ -165,8 +110,6 @@ val JVM_TOOLCHAIN_ENABLED by projectLazy {
*
* 如果[启用 Android Instrumented Test][ENABLE_ANDROID_INSTRUMENTED_TESTS], 将会配置使用 Android SDK 配置真 Android target,
* `androidMain` 将能访问 Android SDK, 也能获得针对 Android IDE 错误检查.
*
* @see configureNativeTargetsHierarchical
*/
fun Project.configureJvmTargetsHierarchical(androidNamespace: String) {
extensions.getByType(KotlinMultiplatformExtension::class.java).apply {
@ -220,401 +163,6 @@ fun Project.configureJvmTargetsHierarchical(androidNamespace: String) {
}
}
/**
* Target 结构:
* ```
* common
* |
* /---------------+---------------\
* jvmBase native
* / \ / \
* jvm android unix \
* / \ mingwX64
* / \
* darwin linuxX64
* |
* *
* <darwin targets>
* ```
*
* `<darwin targets>`: macosX64, macosArm64
*
* @see configureJvmTargetsHierarchical
*/
fun KotlinMultiplatformExtension.configureNativeTargetsHierarchical(
project: Project
) {
if (NATIVE_TARGETS.isEmpty()) return
val nativeMainSets = mutableListOf<KotlinSourceSet>()
val nativeTestSets = mutableListOf<KotlinSourceSet>()
val nativeTargets =
mutableListOf<KotlinTarget>() // actually KotlinNativeTarget, but KotlinNativeTarget is an internal API (complained by IDEA)
fun KotlinMultiplatformExtension.addNativeTarget(
preset: KotlinTargetPreset<*>,
): KotlinTarget {
val target = targetFromPreset(preset, preset.name)
nativeMainSets.add(target.compilations[MAIN_COMPILATION_NAME].kotlinSourceSets.first())
nativeTestSets.add(target.compilations[TEST_COMPILATION_NAME].kotlinSourceSets.first())
nativeTargets.add(target)
return target
}
// project.configureNativeInterop("main", project.projectDir.resolve("src/nativeMainInterop"), nativeTargets)
// project.configureNativeInterop("test", project.projectDir.resolve("src/nativeTestInterop"), nativeTargets)
val sourceSets = project.objects.domainObjectContainer(KotlinSourceSet::class.java)
.apply { addAll(project.kotlinSourceSets.orEmpty()) }
val commonMain by sourceSets.getting
val commonTest by sourceSets.getting
val nativeMain by lazy {
this.sourceSets.maybeCreate("nativeMain").apply {
dependsOn(commonMain)
}
}
val nativeTest by lazy {
this.sourceSets.maybeCreate("nativeTest").apply {
dependsOn(commonTest)
}
}
if (UNIX_LIKE_TARGETS.isNotEmpty()) {
val unixMain by lazy {
this.sourceSets.maybeCreate("unixMain").apply {
dependsOn(nativeMain)
}
}
val unixTest by lazy {
this.sourceSets.maybeCreate("unixTest").apply {
dependsOn(nativeTest)
}
}
val darwinMain by lazy {
this.sourceSets.maybeCreate("darwinMain").apply {
dependsOn(unixMain)
}
}
val darwinTest by lazy {
this.sourceSets.maybeCreate("darwinTest").apply {
dependsOn(unixTest)
}
}
presets.filter { it.name in MAC_TARGETS }.forEach { preset ->
addNativeTarget(preset).run {
compilations[MAIN_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(darwinMain) }
compilations[TEST_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(darwinTest) }
}
}
presets.filter { it.name in LINUX_TARGETS }.forEach { preset ->
addNativeTarget(preset).run {
compilations[MAIN_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(unixMain) }
compilations[TEST_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(unixTest) }
}
}
}
presets.filter { it.name in WIN_TARGETS }.forEach { preset ->
addNativeTarget(preset).run {
compilations[MAIN_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(nativeMain) }
compilations[TEST_COMPILATION_NAME].kotlinSourceSets.forEach { it.dependsOn(nativeTest) }
}
}
// Workaround from https://youtrack.jetbrains.com/issue/KT-52433/KotlinNative-Unable-to-generate-framework-with-Kotlin-1621-and-Xcode-134#focus=Comments-27-6140143.0-0
project.tasks.withType<KotlinNativeLink>().configureEach {
val properties = listOf(
"ios_arm32", "watchos_arm32", "watchos_x86"
).joinToString(separator = ";") { "clangDebugFlags.$it=-Os" }
kotlinOptions.freeCompilerArgs += listOf(
"-Xoverride-konan-properties=$properties"
)
}
// WIN_TARGETS.forEach { targetName ->
// val target = targets.getByName(targetName) as KotlinNativeTarget
// if (!IDEA_ACTIVE && HOST_KIND == HostKind.WINDOWS) {
// // add release test to run on CI
// project.afterEvaluate {
// target.findOrCreateTest(NativeBuildType.RELEASE) {
// // use linkReleaseTestMingwX64 for mingwX64Test to save memory
// tasks.getByName("mingwX64Test", KotlinNativeTest::class)
// .executable(linkTask) { linkTask.binary.outputFile }
// }
// }
// }
// }
// Register platform tasks, e.g. linkDebugSharedHost
project.afterEvaluate {
val targetName = HOST_KIND.targetName
val targetNameCapitalized = targetName.capitalize()
project.tasks.run {
listOf(
"compileKotlin",
"linkDebugTest",
"linkReleaseTest",
"linkDebugShared",
"linkReleaseShared",
"linkDebugStatic",
"linkReleaseStatic",
).forEach { name ->
findByName("$name$targetNameCapitalized")?.let { plat ->
register("${name}Host") {
group = "mirai"
description = "Run ${plat.name} which can be run on the current Host."
dependsOn(plat)
}
}
}
findByName("${targetName}Test")?.let { plat ->
register("hostTest") {
group = "mirai"
description = "Run ${plat.name} which can be run on the current Host."
dependsOn(plat)
}
}
}
project.disableCrossCompile() // improve speed
}
}
fun KotlinMultiplatformExtension.configureNativeTargetBinaries(project: Project) {
if (!System.getProperty("mirai.native.binaries", "false").toBoolean()) {
// Must enable KotlinNativeLink by argument "mirai.native.link".
// :mirai-core:linkReleaseSharedMacosX64
return
// disableNativeLinkTasks(project)
}
NATIVE_TARGETS.forEach { targetName ->
val target = targets.getByName(targetName) as KotlinNativeTarget
target.binaries {
sharedLib(listOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE)) {
baseName = project.name.lowercase(Locale.ROOT).replace("-", "")
}
staticLib(listOf(NativeBuildType.DEBUG, NativeBuildType.RELEASE)) {
baseName = project.name.lowercase(Locale.ROOT).replace("-", "")
}
}
}
}
private fun disableNativeLinkTasks(project: Project) {
project.tasks.withType<KotlinNativeLink>()
.filter { it.binary.outputKind != NativeOutputKind.TEST }
.forEach {
it.enabled = false
project.logger.warn("Disabling KotlinNativeLink: ${it.path}")
}
}
private fun KotlinNativeTarget.findOrCreateTest(buildType: NativeBuildType, configure: TestExecutable.() -> Unit) =
binaries.findTest(buildType)?.apply(configure) ?: binaries.test(listOf(buildType), configure)
// e.g. Linker will try to link curl for mingwX64 but this can't be done on macOS.
fun Project.disableCrossCompile() {
project.afterEvaluate {
if (HOST_KIND != HostKind.MACOS_ARM64) {
disableTargetLink(this, HostKind.MACOS_ARM64.targetName)
}
if (HOST_KIND != HostKind.MACOS_X64) {
disableTargetLink(this, HostKind.MACOS_X64.targetName)
}
if (HOST_KIND != HostKind.WINDOWS) {
WIN_TARGETS.forEach { target -> disableTargetLink(this, target) }
}
if (HOST_KIND != HostKind.LINUX) {
LINUX_TARGETS.forEach { target -> disableTargetLink(this, target) }
}
}
}
private fun disableTargetLink(project: Project, target: String) {
// Don't disable compileKotlin tasks. These tasks ensure code syntax is correct.
project.tasks.findByName("linkDebugTest${target.titlecase()}")?.enabled = false
project.tasks.findByName("linkReleaseTest${target.titlecase()}")?.enabled = false
project.tasks.findByName("linkDebugShared${target.titlecase()}")?.enabled = false
project.tasks.findByName("linkReleaseShared${target.titlecase()}")?.enabled = false
project.tasks.findByName("linkDebugStatic${target.titlecase()}")?.enabled = false
project.tasks.findByName("linkReleaseStatic${target.titlecase()}")?.enabled = false
project.tasks.findByName("${target}Test")?.enabled = false
}
private fun Project.linkerDirs(): List<String> {
return listOf(
":mirai-core",
":mirai-core-api",
":mirai-core-utils",
).map {
rootProject.project(it).projectDir.resolve("src/nativeMainInterop/target/debug/").absolutePath
}
}
private fun Project.includeDirs(): List<String> {
return listOf(
":mirai-core",
":mirai-core-api",
":mirai-core-utils",
).map {
rootProject.project(it).projectDir.resolve("src/nativeMainInterop/").absolutePath
}
}
private fun Project.configureNativeInterop(
compilationName: String, nativeInteropDir: File, nativeTargets: MutableList<KotlinNativeTarget>
) {
val crateName = project.name.replace("-", "_") + "_i"
if (nativeInteropDir.exists() && nativeInteropDir.isDirectory && nativeInteropDir.resolve("build.rs").exists()) {
val kotlinDylibName = project.name.replace("-", "_") + "_i"
val headerName = "$crateName.h"
val rustLibDir = nativeInteropDir.resolve("target/debug/")
var interopTaskName = ""
configure(nativeTargets) {
interopTaskName = compilations.getByName(compilationName).cinterops.create(compilationName) {
defFile(nativeInteropDir.resolve("interop.def"))
val headerFile = nativeInteropDir.resolve(headerName)
if (headerFile.exists()) headers(headerFile)
}.interopProcessingTaskName
binaries {
sharedLib {
linkerOpts("-v")
linkerOpts("-L${rustLibDir.absolutePath.replace("\\", "/")}")
// linkerOpts("-lmirai_core_utils_i")
linkerOpts("-undefined", "dynamic_lookup")
baseName = project.name
}
getTest(NativeBuildType.DEBUG).apply {
linkerOpts("-v")
linkerOpts("-L${rustLibDir.absolutePath.replace("\\", "/")}")
linkerOpts("-lmirai_core_utils_i")
// linkerOpts("-undefined", "dynamic_lookup")
}
}
}
val cbindgen = tasks.register("cbindgen${compilationName.titlecase()}") {
group = "mirai"
description = "Generate C Headers from Rust"
inputs.files(project.objects.fileTree().from(nativeInteropDir.resolve("src"))
.filterNot { it.name == "bindings.rs" })
outputs.file(nativeInteropDir.resolve(headerName))
doLast {
exec {
workingDir(nativeInteropDir)
commandLine(
"cbindgen", "--config", "cbindgen.toml", "--crate", crateName, "--output", headerName
)
}
}
}
val generateRustBindings = tasks.register("generateRustBindings${compilationName.titlecase()}") {
group = "mirai"
description = "Generates Rust bindings for Kotlin"
dependsOn(cbindgen)
}
afterEvaluate {
val cinteropTask = tasks.getByName(interopTaskName)
cinteropTask.mustRunAfter(cbindgen)
generateRustBindings.get().dependsOn(cinteropTask)
}
val bindgen = tasks.register("bindgen${compilationName.titlecase()}") {
group = "mirai"
val bindingsPath = nativeInteropDir.resolve("src/bindings.rs")
val headerFile = buildDir.resolve("bin/native/debugShared/lib${kotlinDylibName}_api.h")
inputs.files(headerFile)
outputs.file(bindingsPath)
mustRunAfter(tasks.findByName("linkDebugSharedNative"))
doLast {
exec {
workingDir(nativeInteropDir)
// bindgen input.h -o bindings.rs
commandLine(
"bindgen",
headerFile,
"-o", bindingsPath,
)
}
}
}
tasks.register("generateKotlinBindings${compilationName.titlecase()}") {
group = "mirai"
description = "Generates Kotlin bindings for Rust"
dependsOn(bindgen)
dependsOn(tasks.findByName("linkDebugSharedNative"))
}
var targetCompilation: KotlinNativeCompilation? = null
configure(nativeTargets) {
val compilations = compilations.filter { nativeInteropDir.name.contains(it.name, ignoreCase = true) }
check(compilations.isNotEmpty()) { "Should be at lease one corresponding native compilation, but found 0" }
targetCompilation = compilations.single()
// targetCompilation!!.compileKotlinTask.dependsOn(cbindgen)
// tasks.getByName("cinteropNative$name").dependsOn(cbindgen)
}
if (targetCompilation != null) {
val compileRust = tasks.register("compileRust${compilationName.titlecase()}") {
group = "mirai"
inputs.files(nativeInteropDir.resolve("src"))
outputs.file(rustLibDir.resolve("lib$crateName.dylib"))
// dependsOn(targetCompilation!!.compileKotlinTask)
dependsOn(bindgen)
dependsOn(tasks.findByName("linkDebugSharedNative")) // dylib to link
doLast {
exec {
workingDir(nativeInteropDir)
commandLine(
"cargo",
"build",
"--color", "always",
"--all",
// "--", "--color", "always", "2>&1"
)
}
}
}
tasks.getByName("assemble").dependsOn(compileRust)
}
}
}
private fun Project.configureNativeLinkOptions(nativeTargets: MutableList<KotlinNativeTarget>) {
configure(nativeTargets) {
binaries {
for (buildType in NativeBuildType.values()) {
findTest(buildType)?.apply {
linkerOpts("-v")
linkerOpts(*linkerDirs().map { "-L$it" }.toTypedArray())
linkerOpts("-undefined", "dynamic_lookup") // resolve symbol in runtime
}
}
}
}
}
fun String.titlecase(): String {
if (this.isEmpty()) return this
val c = get(0)

View File

@ -11,16 +11,12 @@
mirai 上传到 Maven Central 的预编译模块列表如下表所示。在表中列举的平台即为你的项目可以使用的平台。
如果你使用了一个不受支持的平台,在构建项目时将会得到来自 Gradle 的依赖解决错误。
mirai 实际上能支持 arm 架构,但由于 GitHub Actions 均为 x86 主机,而现阶段配置 Kotlin 交叉编译也较困难,就没有支持。
没有支持其他小众平台是因为实用性有限。若有需求欢迎 PR。
mirai 曾在 2.13.0 ~ 2.15.0-RC不包含支持编译到 macOS、Window、Linux 平台。自 2.15.0-RC 已完全删除对这些平台的支持。
| 发布平台名称 | 描述 |
|------------|------------------|
| jvm | JVM |
| android | Android (Dalvik) |
| mingwX64 | Windows x64 |
| macosX64 | macOS x64 |
| linuxX64 | Linux x64 |
| 发布平台名称 | 描述 |
|---------|------------------|
| jvm | JVM |
| android | Android (Dalvik) |
## 添加依赖
@ -49,7 +45,7 @@ kotlin {
## 解决问题
如果你在使用多平台项目时遇到问题那应该是正常的。Kotlin 多平台项目在 1.7 仍然是一个测试版功能。mirai 的 native 平台支持是在 2.13.0 才提供。欢迎在 issues 提交多平台相关问题。
如果你在使用多平台项目时遇到问题那应该是正常的。Kotlin 多平台项目在 1.7 仍然是一个测试版功能。欢迎在 issues 提交多平台相关问题。
> 依赖配置完成,
> - [回到 Mirai 文档索引](README.md#使用-mirai)

View File

@ -105,40 +105,26 @@ core'
[HMPP]: https://kotlinlang.org/docs/multiplatform-discover-project.html
core 三个模块都使用 Kotlin [HMPP] 功能,同时支持 JVM 和 Native
core 三个模块都使用 Kotlin [HMPP] 功能,同时支持 JVM 和 Android
两种平台。你可以在 [Kotlin 官方文档][HMPP] 了解 HMPP 模式。
core 的编译目标层级结构如图所示:
```
common
|
/---------------+---------------\
jvmBase native
/ \ / \
jvm android unix \
/ \ mingwX64
/ \
darwin linuxX64
|
*
<darwin targets>
common
/ \
jvm android
```
| 发布平台名称 | 描述 |
|------------|------------------|
| jvm | JVM |
| android | Android (Dalvik) |
| mingwX64 | Windows x64 |
| macosX64 | macOS x64 |
| macosArm64 | macOS arm64 |
| linuxX64 | Linux x64 |
| 发布平台名称 | 描述 |
|---------|------------------|
| jvm | JVM |
| android | Android (Dalvik) |
备注:
- common 包含全平台通用代码,绝大部分代码都位于 common
- jvmBase 包含针对 JVM 平台的通用代码;
- `<darwin targets>` 为 macOSiOSWatchOS 等 Apple 平台目标。
## 开发提示
@ -175,32 +161,24 @@ mirai.enable.jvmtoolchain.special=false
所有目标默认都启用。
**注意**,在关闭一个目标后,将无法编辑该目标的相关源集的源码。关闭部分 native 目标后也可能会影响 native 目标平台原生接口的数据类型。
因此若非主机性能太差或在 CI 机器运行,**不建议**关闭 native 目标。
**注意**,在关闭一个目标后,将无法编辑该目标的相关源集的源码。
因此若非主机性能太差或在 CI 机器运行,**不建议**关闭目标。
[//]: # (备注: 如果要发版, 必须开启全部目标, 否则会导致 metadata 中的平台不全)
- `xxx`:显式启用 `xxx` 目标
- `!xxx`:显式禁用 `xxx` 目标
- `native`:显式启用所有 native 目标
- `!native`:禁用没有显式启用的所有 native 目标
- `others`:显式启用其他所有所有目标
- `!others`:禁用没有显式启用的所有目标
其中 xxx 表示构建目标名称。可用的目标名称有(区分大小写):`jvm`、`android`、`macosX64`、`macosArm64`、`mingwX64`、`linuxX64`
其中 xxx 表示构建目标名称。可用的目标名称有(区分大小写):`jvm`、`android`
```
# 禁用所有 native 目标,启用其他目标(启用 jvm 和 android
projects.mirai-core.targets=!native;others
# 启用 `jvm``android` 目标,禁用其他所有目标(禁用所有 native 目标)
projects.mirai-core.targets=jvm;android;!others
# 只启用 `jvm` 目标,禁用其他所有目标
# 只启用 `jvm` 目标,禁用其他所有目标 (Android)
projects.mirai-core.targets=jvm;!others
# 指定启用 `jvm``macosX64` 目标,禁用其他所有目标
projects.mirai-core.targets=jvm;macosX64;!others
# 启用 `jvm``android` 目标
projects.mirai-core.targets=jvm;android
```
### 直接启动 mirai-core 本地测试
@ -209,9 +187,7 @@ projects.mirai-core.targets=jvm;macosX64;!others
在 JVM 平台直接启动 mirai-core, 见 [mirai-core/jvmTest](/mirai-core/src/jvmTest/README.md)
在 native 平台直接启动 mirai-core, 见 [mirai-core/nativeTest](/mirai-core/src/nativeTest/kotlin/local/README.md)
## 构建 mirai 项目 JAR 以及动态链接库
## 构建 mirai 项目 JAR
查看 [Building](building/README.md)

View File

@ -29,5 +29,3 @@
./gradlew publishMiraiArtifactsToMavenLocal "-Dprojects.mirai-core.targets=jvm;android;!others"
```
若上述命令不工作,尝试在 Android Studio 中打开项目并在 Studio 的终端中执行命令。
- 我是 mirai native 用户,我需要 `.dll/.so/.dylib`
你不能简单地构建这些动态链接库,因为它们需要依赖。阅读 [BuildingCoreNative](building/BuildingCoreNative.md)。

View File

@ -1,194 +0,0 @@
[OpenSSL.def]: ../../mirai-core/src/nativeMain/cinterop/OpenSSL.def
Kotlin 会自动配置 Native 编译器,要构建 Mirai 的 Native 目标还需要准备相关依赖。
### 操作系统条件
主机操作系统为以下任一:
- Windows x86_64 (amd64)
- macOS x86_64 (amd64)
- macOS aarch64 (arm64)
- Linux x86_64 (amd64)
注意32 位操作系统不受支持。未列举的操作系统不受支持。
目前 Kotlin 对交叉编译支持有限,只能在一个主机上编译该主机平台的目标。例如在 Windows x86_64 主机上只能编译
Windows x86_64 目标;在 macOS aarch64 主机上只能编译 macOS aarch64 目标。
与其他 Native 语言相同Kotlin 的应用使用依赖时同样需要配置链接mirai
已经配置了常用目录。也可以在 `mirai-core/src/nativeMain/cinterop/OpenSSL.def`
修改 `linkerOpts` 即链接器参数,以增加自定义路径。
### 性能提示
在编译和链接时可能需要大量内存,请使用至少拥有 8GB 内存的主机。使用 32GB 内存的主机可以获得不错的体验。mirai
默认启用多项目同时编译,编译时可能会使用大量主机资源。
如果主机可用内存较低,请不要执行 `./gradlew assemble`
编译全部项目,这可能会导致内存溢出,也将会导致编译缓慢。可以单独为某个模块执行批量编译,如 `./gradlew :mirai-console:assemble`
#### 编译耗时
若使用 Apple M1 Max 或同等级 CPU (AMD R7 5800X / Intel i7-12700K / Intel
i9-12950HX),单独执行 `./gradlew assemble` 编译并连接全部项目 (含动态链接库和静态链接库) 需时约 9
分钟。单独执行 `./gradlew check` 需约 4 分钟。
但在 GitHub 的 2 核心 CPU Actions 机器上执行 `assemble` 通常需要约 40 分钟,。
### 安装 OpenSSL
所有上述主机都需要进行这一步。
可以访问 OpenSSL 官网 `https://curl.se/download.html` 安装。
#### 在 Ubuntu 通过 Aptitude 安装 OpenSSL
```shell
$ sudo apt install libssl-dev # 安装 OpenSSL
$ sudo apt install gcc-multilib # 若遇到链接问题可额外尝试此命令
```
#### 在 macOS 通过 Homebrew 安装 OpenSSL
```shell
$ brew install openssl@3
```
注意:若遇到无法链接等问题,可以尝试通过源码编译安装。
#### 在 macOS 或 Linux 通过源码编译安装 OpenSSL
请参考 [OpenSSL 文档](https://github.com/openssl/openssl/blob/master/INSTALL.md#prerequisites)
准备 OpenSSL 的要求。
以下命令可能会帮助你(这是 mirai 的 GitHub Actions 使用的命令)。
```shell
$ git clone https://github.com/openssl/openssl.git --recursive
$ cd openssl
$ git checkout tags/openssl-3.0.3
$ ./Configure --prefix=/opt/openssl --openssldir=/usr/local/ssl
$ make
$ sudo make install
```
若在 Ubuntu 遇到链接问题,可额外尝试此命令:
```shell
$ sudo apt install gcc-multilib
```
#### 在 Windows 通过 vcpkg 安装 OpenSSL
你需要提前安装 [vcpkg](https://github.com/microsoft/vcpkg/blob/master/README_zh_CN.md)
以下命令可能会帮助你(这是 mirai 的 GitHub Actions 使用的命令)。
```powershell
echo "set(VCPKG_BUILD_TYPE release)" | Out-File -FilePath "$env:VCPKG_INSTALLATION_ROOT\triplets\x64-windows.cmake" -Encoding utf8 -Append
vcpkg install openssl:x64-windows curl[core, ssl]: x64-windows
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\crypto.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcrypto.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\ssl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libssl.lib
New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\curl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcurl.lib
echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
```
由于链接器只识别不包含 `lib` 前缀的文件(如 `ssl.lib` 而非 `libssl.lib`vcpkg 编译产物为后者),上述 `New-Item` 创建一个无 `lib` 前缀的链接指向库文件。
注意:
- 你将需要修改链接器配置(位于 `mirai-core/src/nativeMain/cinterop/OpenSSL.def`
),增加 `linkerOpts``compilerOpts` 指向你本地安装的路径。
- 不要将修改路径后的 `OpenSSL.def` 通过 Git 推送到 mirai 仓库或 PR。
#### 在 Windows 通过源码编译安装 OpenSSL
在 Windows可通过源码编译安装请使用 Command Prompt (cmd)。
你需要提前安装 [Git](https://git-scm.com/)
和 [Microsoft Visual Studio](https://visualstudio.microsoft.com/zh-hans/)
,并修改以下命令中的路径。
请参考 [OpenSSL 文档](https://github.com/openssl/openssl/blob/master/INSTALL.md#prerequisites)
准备 OpenSSL 的要求。
以下命令可能会帮助你(这是 mirai 的 GitHub Actions 使用过的命令)。
```shell
git clone https://github.com/openssl/openssl.git --recursive
cd openssl
git checkout tags/openssl-3.0.3
perl Configure VC-WIN64A --prefix=C:/openssl --openssldir=C:/openssl/ssl no-asm
"C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 && nmake && nmake install
```
注意:
- `--prefix=C:/openssl --openssldir=C:/openssl/ssl` 表示将 OpenSSL
安装在 `C:/openssl`mirai
已经配置会使用此路径寻找链接库。你也可以更换为其他路径,但就需要同步修改配置(位于 `mirai-core/src/nativeMain/cinterop/OpenSSL.def`
- 不要将修改路径后的 `OpenSSL.def` 通过 Git 推送到 mirai 仓库或 PR。
### 安装 cURL
mirai 在 Windows 上使用
cURL在其他平台使用 [Ktor CIO](https://ktor.io/docs/http-client-engines.html#cio)
,因此只有 Windows 系统需要进行这一步。
可以访问 cURL 官网 <https://curl.se/download.html> 安装。
提示:如果在[链接](#链接并测试)时遇到找不到 cURL
相关符号的问题,请尝试修改链接器参数。尽管 `mirai-core/src/nativeMain/cinterop/OpenSSL.def`
是用于 `OpenSSL.def` 的,也可以在这个文件配置 cURL 路径。
### 编译
在任意主机上可以执行所有目标的 Kotlin 编译,但不能执行链接。要执行特定目标的编译,运行 Gradle
任务 `compileKotlinXXX`,其中 `XXX` 可以是:`MacosX64`、`MacosArm64`、`MingwX64`
`LinuxX64`
也可以执行 `compileKotlinHost`,将自动根据当前主机选择合适的目标。
### 链接并测试
执行 core 模块的 `hostTest`,将根据主机选择合适的测试并运行。
详情参考 [Kotlin 官方文档](https://kotlinlang.org/docs/multiplatform-run-tests.html)
### 链接并构建动态链接库
注意,只有 mirai-core 可以构建可用的动态链接库。所有动态链接库和静态链接库的构建都是默认关闭的,需要使用 `-Dmirai.native.binaries=true` 才能启用。
## 构建 core 的 Native 目标
在提供 `-Dmirai.native.binaries=true` 参数的情况下,执行 `:mirai-core:linkDebugSharedHost`
`:mirai-core:linkReleaseSharedHost`。Debug 版本会保留调试符号,能显示完整错误堆栈;而
Release 拥有更小体积(比 Debug 减小 50%)。
示例:
```shell
./gradlew :mirai-core:linkDebugSharedHost "-Dmirai.native.binaries=true"
./gradlew :mirai-core:linkReleaseSharedMacoxX64 "-Dmirai.native.binaries=true"
```
这也会同时生成一个头文件(`.h`
)供交互使用。详情查看 [Kotlin 官方文档](https://kotlinlang.org/docs/native-c-interop.html)
可以在 `mirai-core/build/bin/macosArm64/debugShared/` 类似路径找到生成的动态链接库和头文件。
### 链接并构建静态链接库
注意,只有 mirai-core 可以构建可用的静态链接库。
与构建动态链接库类似,在提供 `-Dmirai.native.binaries=true` 参数的情况下,执行 `:mirai-core:linkDebugStaticHost`
`:mirai-core:linkReleaseStaticHost`。Debug 版本会保留调试符号,能显示完整错误堆栈;而
Release 拥有更小体积(比 Debug 减小 50%)。
可以在 `mirai-core/build/bin/macosArm64/debugStatic/` 类似路径找到生成的静态链接库和头文件。

View File

@ -31,10 +31,6 @@ Gradle 任务:
查看 [BuildingCoreAndroid](BuildingCoreAndroid.md)。
### 构建 core 的 Native 目标
查看 [BuildingCoreNative](BuildingCoreNative.md)。
## 构建 IntelliJ 插件
可通过如下命令构建 IntelliJ 平台 IDE 的插件。构建成功的插件将可以在 `mirai-console/tools/intellij-plugin/build/distribution` 中找到。

View File

@ -12,7 +12,6 @@ kotlin.incremental.multiplatform=true
org.gradle.jvmargs=-Xmx7000m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED
org.gradle.parallel=true
org.gradle.vfs.watch=true
kotlin.native.binary.memoryModel=experimental
#kotlin.mpp.enableCompatibilityMetadataVariant=true
systemProp.org.gradle.internal.publish.checksums.insecure=true
gnsp.disableApplyOnlyOnRootProjectEnforcement=true
@ -20,7 +19,6 @@ mirai.android.target.api.level=21
# Enable if you want to use mavenLocal for both Gradle plugin and project dependencies resolutions.
systemProp.use.maven.local=false
org.gradle.caching=true
kotlin.native.ignoreIncorrectDependencies=true
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.stability.nowarn=true
kotlin.mpp.androidSourceSetLayoutVersion=2

View File

@ -22,7 +22,6 @@ kotlin {
apply(plugin = "explicit-api")
configureJvmTargetsHierarchical("net.mamoe.mirai.compiler.annotations")
configureNativeTargetsHierarchical(project)
}
configureMppPublishing()

View File

@ -32,8 +32,6 @@ kotlin {
configureJvmTargetsHierarchical("net.mamoe.mirai")
configureNativeTargetsHierarchical(project)
sourceSets {
val commonMain by getting {
dependencies {
@ -87,11 +85,6 @@ kotlin {
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
}
}
findByName("nativeMain")?.apply {
dependencies {
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -16,15 +16,10 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.announcement.OfflineAnnouncement.Companion.serializer
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.copy
import net.mamoe.mirai.utils.map
import net.mamoe.mirai.utils.safeCast
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
* 表示在本地构建的 [Announcement].
@ -95,7 +90,6 @@ public sealed interface OfflineAnnouncement : Announcement {
* 依据 [from] 创建 [OfflineAnnouncement]. [from] 类型为 [OfflineAnnouncement] 则直接返回 [from].
* @since 2.7
*/
@CName("", "OfflineAnnouncement_new")
public inline fun OfflineAnnouncement(from: Announcement): OfflineAnnouncement =
OfflineAnnouncement.from(from)
@ -105,7 +99,6 @@ public inline fun OfflineAnnouncement(from: Announcement): OfflineAnnouncement =
* @param parameters 可选的附加参数
* @since 2.7
*/
@CName("", "OfflineAnnouncement_new2")
public inline fun OfflineAnnouncement(
content: String,
parameters: AnnouncementParameters = AnnouncementParameters.DEFAULT
@ -118,7 +111,6 @@ public inline fun OfflineAnnouncement(
* @see AnnouncementParametersBuilder
* @since 2.7
*/
@CName("", "OfflineAnnouncement_new3")
public inline fun OfflineAnnouncement(
content: String,
parameters: AnnouncementParametersBuilder.() -> Unit

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -24,10 +24,6 @@ import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
@ -88,7 +84,6 @@ public data class At(
* @see Member.at
*/
@JvmSynthetic
@CName("", "At_new")
public inline fun At(user: UserOrBot): At = At(user.id)
/**

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -22,11 +22,6 @@ import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
@ -234,7 +229,6 @@ public interface OfflineAudio : Audio {
* @since 2.7
*/
@JvmSynthetic
@CName("", "OfflineAudio_new")
public inline fun OfflineAudio(
filename: String,
fileMd5: ByteArray,
@ -248,7 +242,6 @@ public inline fun OfflineAudio(
* @since 2.7
*/
@JvmSynthetic
@CName("", "OfflineAudio_new2")
public inline fun OfflineAudio(
onlineAudio: OnlineAudio
): OfflineAudio = OfflineAudio.Factory.from(onlineAudio)

View File

@ -26,11 +26,6 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.map
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
* 文件消息.
@ -133,6 +128,5 @@ internal open class FallbackFileMessageSerializer :
* @since 2.5
*/
@JvmSynthetic
@CName("", "FileMessage_new")
public fun FileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage =
FileMessage.create(id, internalId, name, size)

View File

@ -18,9 +18,6 @@ import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
* 闪照. 闪照的内容取决于 [image] 代表的图片.
@ -97,7 +94,6 @@ public data class FlashImage(
* 将普通图片转换为闪照.
*/
@JvmSynthetic
@CName("", "FlashImage_new")
public inline fun FlashImage(imageId: String): FlashImage = FlashImage.from(imageId)
/**

View File

@ -47,11 +47,6 @@ import net.mamoe.mirai.message.data.Image.Key.queryUrl
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
* 自定义表情 (收藏的表情) 和普通图片.
@ -487,7 +482,6 @@ public interface Image : Message, MessageContent, CodableMessage {
* @see IMirai.createImage
*/
@JvmSynthetic
@CName("", "Image_new")
public fun Image(imageId: String): Image = Builder.newBuilder(imageId).build()
/**
@ -497,7 +491,6 @@ public fun Image(imageId: String): Image = Builder.newBuilder(imageId).build()
* @since 2.9.0
*/
@JvmSynthetic
@CName("", "Image_new2")
public inline fun Image(imageId: String, builderAction: Builder.() -> Unit = {}): Image =
Builder.newBuilder(imageId).apply(builderAction).build()

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -21,11 +21,6 @@ import net.mamoe.mirai.IMirai
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
import kotlin.native.CName
/**
* mirai 尚未支持的消息类型.
@ -81,5 +76,4 @@ public interface UnsupportedMessage : MessageContent {
* @see UnsupportedMessage.create
*/
@JvmSynthetic
@CName("", "UnsupportedMessage_new")
public inline fun UnsupportedMessage(struct: ByteArray): UnsupportedMessage = UnsupportedMessage.create(struct)

View File

@ -24,8 +24,6 @@ import net.mamoe.mirai.event.events.BotOfflineEvent
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.jvm.*
import kotlin.native.CName
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@ -551,7 +549,6 @@ public open class BotConfiguration : AbstractBotConfiguration() { // open for Ja
* @since 2.3
*/
@JvmSynthetic
@CName("", "BotConfiguration_new2")
public inline fun BotConfiguration(block: BotConfiguration.() -> Unit): BotConfiguration {
return BotConfiguration().apply(block)
}

View File

@ -1,107 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.contact
import io.ktor.utils.io.core.*
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.OverFileSizeMaxException
import kotlin.coroutines.cancellation.CancellationException
/**
* 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], [][Group].
*/
@NotStableForInheritance
public actual interface Contact : ContactOrBot, CoroutineScope {
/**
* 这个联系对象所属 [Bot].
*/
public actual override val bot: Bot
/**
* 可以是 QQ 号码或者群号码.
*
* @see User.id
* @see Group.id
*/
public actual override val id: Long
/**
* 向这个对象发送消息.
*
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see MessagePreSendEvent 发送消息前事件
* @see MessagePostSendEvent 发送消息后事件
*
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出
* @throws MessageTooLargeException 当消息过长时抛出
* @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty])
*
* @return 消息回执. [引用][MessageReceipt.quote] [撤回][MessageReceipt.recall] 这条消息.
*/
public actual suspend fun sendMessage(message: Message): MessageReceipt<Contact>
/**
* 发送纯文本消息
* @see sendMessage
*/
public actual suspend fun sendMessage(message: String): MessageReceipt<Contact> = sendMessage(message.toPlainText())
/**
* 上传一个 [资源][ExternalResource] 作为图片以备发送.
*
* **无论上传是否成功都不会关闭 [resource]. 需要调用方手动关闭资源**
*
* 也可以使用其他扩展: [ExternalResource.uploadAsImage] 使用 [Input] 等上传.
*
* @see Image 查看有关图片的更多信息, 如上传图片
*
* @see BeforeImageUploadEvent 图片发送前事件, 可拦截.
* @see ImageUploadEvent 图片发送完成事件, 不可拦截.
*
* @see ExternalResource
*
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时抛出. (最大大小约为 20 MB, mirai 限制的大小为 30 MB)
*/
public actual suspend fun uploadImage(resource: ExternalResource): Image
public actual companion object {
/**
* 将资源作为单独的图片消息发送给 [this]
*
* @see Contact.sendMessage 最终调用, 发送消息.
*/
public actual suspend fun <C : Contact> C.sendImage(resource: ExternalResource): MessageReceipt<C> {
return this.uploadImage(resource).sendTo(this)
}
/**
* 将文件作为图片上传, 但不发送
* @throws OverFileSizeMaxException
*/
@kotlin.internal.LowPriorityInOverloadResolution
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXTENSION_SHADOWED_BY_MEMBER")
@Throws(OverFileSizeMaxException::class, CancellationException::class)
public actual suspend fun Contact.uploadImage(resource: ExternalResource): Image {
return uploadImage(resource)
}
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.contact
import net.mamoe.mirai.contact.file.RemoteFiles
import net.mamoe.mirai.utils.NotStableForInheritance
/**
* 支持文件操作的 [Contact]. 目前仅 [Group].
*
* 获取文件操作相关示例: [RemoteFiles]
*
* @since 2.5
*
* @see RemoteFiles
*/
@NotStableForInheritance
public actual interface FileSupported : Contact {
/**
* 获取远程文件列表 (管理器).
*
* @since 2.8
*/
public actual val files: RemoteFiles
}

View File

@ -1,153 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.contact.file
import kotlinx.coroutines.flow.Flow
import net.mamoe.mirai.contact.PermissionDeniedException
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.ProgressionCallback
/**
* 绝对目录标识. 精确表示一个远程目录. 不会受同名文件或目录的影响.
*
* @since 2.8
* @see RemoteFiles
* @see AbsoluteFile
* @see AbsoluteFileFolder
*/
@Suppress("SEALED_INHERITOR_IN_DIFFERENT_MODULE")
@NotStableForInheritance
public actual interface AbsoluteFolder : AbsoluteFileFolder {
/**
* 当前快照中文件数量, 当有文件更新时(上传/删除文件) 该属性不会更新.
*
* 只可能通过 [refresh] 手动刷新
*
* 特别的, 若该目录表示根目录, [contentsCount] 返回 `0`. (无法快速获取)
*/
public actual val contentsCount: Int
/**
* 当该目录为空时返回 `true`.
*/
public actual fun isEmpty(): Boolean = contentsCount == 0
/**
* 返回更新了文件或目录信息 ([lastModifiedTime] ) , 指向相同文件的 [AbsoluteFileFolder].
* 不会更新当前 [AbsoluteFileFolder] 对象.
*
* 当远程文件或目录不存在时返回 `null`.
*
* 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用.
*/
actual override suspend fun refreshed(): AbsoluteFolder?
///////////////////////////////////////////////////////////////////////////
// list children
///////////////////////////////////////////////////////////////////////////
/**
* 获取该目录下所有子目录列表.
*/
public actual suspend fun folders(): Flow<AbsoluteFolder>
/**
* 获取该目录下所有文件列表.
*/
public actual suspend fun files(): Flow<AbsoluteFile>
/**
* 获取该目录下所有文件和子目录列表.
*/
public actual suspend fun children(): Flow<AbsoluteFileFolder>
///////////////////////////////////////////////////////////////////////////
// resolve and upload
///////////////////////////////////////////////////////////////////////////
/**
* 创建一个名称为 [name] 的子目录. 返回成功创建的或已有的子目录. 当目标目录已经存在时则直接返回该目录.
*
* @throws IllegalArgumentException [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出
* @throws PermissionDeniedException 当权限不足时抛出
*/
public actual suspend fun createFolder(name: String): AbsoluteFolder
/**
* 获取一个已存在的名称为 [name] 的子目录. 当该名称的子目录不存在时返回 `null`.
*
* @throws IllegalArgumentException [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出
*/
public actual suspend fun resolveFolder(name: String): AbsoluteFolder?
/**
* 获取一个已存在的 [AbsoluteFileFolder.id] [id] 的子目录. 当该名称的子目录不存在时返回 `null`.
*
* @throws IllegalArgumentException [id] 为空或无效时抛出
*
* @since 2.9.0
*/
public actual suspend fun resolveFolderById(id: String): AbsoluteFolder?
/**
* 精确获取 [AbsoluteFile.id] [id] 的文件. 在目标文件不存在时返回 `null`. [deep] `true` 时还会深入子目录查找.
*/
public actual suspend fun resolveFileById(
id: String,
deep: Boolean
): AbsoluteFile?
/**
* 根据路径获取指向的所有路径为 [path] 的文件列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件.
*/
public actual suspend fun resolveFiles(
path: String
): Flow<AbsoluteFile>
/**
* 根据路径获取指向的所有路径为 [path] 的文件和目录列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件和目录.
*/
public actual suspend fun resolveAll(
path: String
): Flow<AbsoluteFileFolder>
/**
* 上传一个文件到该目录, 返回上传成功的文件标识.
*
* 会在必要时尝试创建远程目录.
*
* ### [filepath]
*
* - 可以是 `foo.txt` 表示该目录下的文件 "foo.txt"
* - 也可以是 `sub/foo.txt` 表示该目录的子目录 "sub" 下的文件 "foo.txt".
* - 或是绝对路径 `/sub/foo.txt` 表示根目录的 "sub" 目录下的文件 "foo.txt"
*
* @param filepath 目标文件名
* @param content 文件内容
* @param callback 下载进度回调, 传递的 `progression` 是已下载字节数.
*
* @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传)
*/
public actual suspend fun uploadNewFile(
filepath: String,
content: ExternalResource,
callback: ProgressionCallback<AbsoluteFile, Long>?,
): AbsoluteFile
public actual companion object {
/**
* 根目录 folder ID.
* @see id
*/
public actual const val ROOT_FOLDER_ID: String = "/"
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.contact.roaming
import kotlinx.coroutines.flow.Flow
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
/**
* 漫游消息记录管理器. 可通过 [RoamingSupported.roamingMessages] 获得.
*
* @since 2.8
* @see RoamingSupported
*/
public actual interface RoamingMessages {
///////////////////////////////////////////////////////////////////////////
// Get list
///////////////////////////////////////////////////////////////////////////
/**
* 查询指定时间段内的漫游消息记录.
*
* 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息.
* 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回.
*
* 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息.
* 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.source`), 判断 [MessageSource.fromId] (发送人).
* 消息的其他*元数据*信息也要通过 [MessageSource] 获取 ( [MessageSource.time] 获取时间).
*
* 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递.
*
* 性能提示: 请在 [filter] 执行筛选, [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响.
*
* @param timeStart 起始时间戳, 单位为秒. 可以为 `0`, 即表示从可以获取的最早的消息起. 负数将会被看是 `0`.
* @param timeEnd 结束时间戳, 单位为秒. 可以为 [Long.MAX_VALUE], 即表示到可以获取的最晚的消息为止. 低于 [timeStart] 的值将会被看作是 [timeStart] 的值.
* @param filter 过滤器.
*/
public actual suspend fun getMessagesIn(
timeStart: Long,
timeEnd: Long,
filter: RoamingMessageFilter?
): Flow<MessageChain>
/**
* 查询所有漫游消息记录.
*
* 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息.
* 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回.
*
* 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息.
* 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.source`), 判断 [MessageSource.fromId] (发送人).
* 消息的其他*元数据*信息也要通过 [MessageSource] 获取 ( [MessageSource.time] 获取时间).
*
* 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递.
*
* 性能提示: 请在 [filter] 执行筛选, [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响.
*
* @param filter 过滤器.
*/
public actual suspend fun getAllMessages(
filter: RoamingMessageFilter?
): Flow<MessageChain> = getMessagesIn(0, Long.MAX_VALUE, filter)
}

View File

@ -1,530 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.event
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedSendChannelException
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot
import net.mamoe.mirai.IMirai
import net.mamoe.mirai.event.ConcurrencyKind.CONCURRENT
import net.mamoe.mirai.event.ConcurrencyKind.LOCKED
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
/**
* 事件通道.
*
* 事件通道是监听事件的入口, 但不负责广播事件. 要广播事件, 使用 [Event.broadcast] [IMirai.broadcastEvent].
*
* ## 获取事件通道
*
* [EventChannel] 不可自行构造, 只能通过 [GlobalEventChannel], [BotEvent], 或基于一个通道的过滤等操作获得.
*
* ### 全局事件通道
*
* [GlobalEventChannel] 是单例对象, 表示全局事件通道, 可以获取到在其中广播的所有事件.
*
* ### [BotEvent] 事件通道
*
* 若只需要监听某个 [Bot] 的事件, 可通过 [Bot.eventChannel] 获取到这样的 [EventChannel].
*
* ## 通道操作
*
* ### 对通道的操作
* - 过滤通道: 通过 [EventChannel.filter]. 例如 `filter { it is BotEvent }` 得到一个只能监听到 [BotEvent] 的事件通道.
* - 转换为 Kotlin 协程 [Channel]: [EventChannel.forwardToChannel]
* - 添加 [CoroutineContext]: [context], [parentJob], [parentScope], [exceptionHandler]
*
* ### 创建事件监听
* - [EventChannel.subscribe] 创建带条件的一个事件监听器.
* - [EventChannel.subscribeAlways] 创建一个总是监听事件的事件监听器.
* - [EventChannel.subscribeOnce] 创建一个只监听单次的事件监听器.
*
* ### 监听器生命周期
*
* 阅读 [EventChannel.subscribe] 以获取监听器生命周期相关信息.
*
* ## kotlinx-coroutines 交互
*
* mirai [EventChannel] 设计比 kotlinx-coroutines [Flow] 稳定版更早.
* [EventChannel] 的功能与 [Flow] 类似, 不过 [EventChannel] [subscribe] (类似 [Flow.collect]) 时有优先级判定, 也允许[拦截][Event.intercept].
*
* ### 通过 [Flow] 接收事件
*
* 使用 [EventChannel.asFlow] 获得 [Flow], 然后可使用 [Flow.collect] 等操作.
*
* ### 转发事件到 [SendChannel]
*
* 使用 [EventChannel.forwardToChannel] 可将事件转发到指定 [SendChannel].
*/
@NotStableForInheritance // since 2.12, before it was `final class`.
public actual abstract class EventChannel<out BaseEvent : Event> @MiraiInternalApi public actual constructor(
public actual val baseEventClass: KClass<out BaseEvent>,
/**
* 此事件通道的默认 [CoroutineScope.coroutineContext]. 将会被添加给所有注册的事件监听器.
*/
public actual val defaultCoroutineContext: CoroutineContext,
) {
/**
* 创建事件监听并将监听结果转发到 [channel]. [Channel.send] 抛出 [ClosedSendChannelException] 时停止 [Listener] 监听和转发.
*
* 返回创建的会转发监听到的所有事件到 [channel] [事件监听器][Listener]. [停止][Listener.complete] 该监听器会停止转发, 不会影响目标 [channel].
*
* [Channel.send] 挂起, 则监听器也会挂起, 也就可能会导致事件广播过程挂起.
*
* 示例:
*
* ```
* val eventChannel: EventChannel<BotEvent> = ...
* val channel = Channel<BotEvent>() // kotlinx.coroutines.channels.Channel
* eventChannel.forwardToChannel(channel, priority = ...)
*
* // 其他地方
* val event: BotEvent = channel.receive() // 挂起并接收一个事件
* ```
*
* @see subscribeAlways
* @see Channel
* @since 2.10
*/
public actual fun forwardToChannel(
channel: SendChannel<@UnsafeVariance BaseEvent>,
coroutineContext: CoroutineContext,
priority: EventPriority,
): Listener<@UnsafeVariance BaseEvent> {
// keep this LOCKED, otherwise compiler will choose the 'inline subscribe' which takes no KClass arg.
return subscribe(baseEventClass, coroutineContext, LOCKED, priority) {
try {
channel.send(it)
ListeningStatus.LISTENING
} catch (_: ClosedSendChannelException) {
ListeningStatus.STOPPED
}
}
}
/**
* 通过 [Flow] 接收此通道内的所有事件.
*
* ```
* val eventChannel: EventChannel<BotEvent> = ...
* val flow: Flow<BotEvent> = eventChannel.asFlow()
*
* flow.collect { // it
* //
* }
*
* flow.filterIsInstance<GroupMessageEvent>.collect { // it: GroupMessageEvent
* // 处理事件 ...
* }
*
* flow.filterIsInstance<FriendMessageEvent>.collect { // it: FriendMessageEvent
* // 处理事件 ...
* }
* ```
*
* 类似于 [SharedFlow], [EventChannel.asFlow] 返回的 [Flow] 永远都不会停止. 因此上述示例 [Flow.collect] 永远都不会正常 (以抛出异常之外的) 结束.
*
* 通过 [asFlow] 接收事件相当于通过 [subscribeAlways] [EventPriority.MONITOR] 监听事件.
*
* **注意**: [context], [parentJob] 等控制 [EventChannel.defaultCoroutineContext] 的操作对 [asFlow] 无效. 因为 [asFlow] 并不创建协程.
*
* @see Flow
* @since 2.12
*/
public actual abstract fun asFlow(): Flow<BaseEvent>
// region transforming operations
/**
* 添加一个过滤器. 过滤器将在收到任何事件之后, 传递给通过 [EventChannel.subscribe] 注册的监听器之前调用.
*
* [filter] 返回 `true`, 该事件将会被传给监听器. 否则将会被忽略, **监听器继续监听**.
*
* ## 线性顺序
* 多个 [filter] 的处理是线性且有顺序的. 若一个 [filter] 已经返回了 `false` (代表忽略这个事件), 则会立即忽略, 而不会传递给后续过滤器.
*
* 示例:
* ```
* GlobalEventChannel // GlobalEventChannel 会收到全局所有事件, 事件类型是 Event
* .filterIsInstance<BotEvent>() // 过滤, 只接受 BotEvent
* .filter { event: BotEvent ->
* // 此时的 event 一定是 BotEvent
* event.bot.id == 123456 // 再过滤 event 的 bot.id
* }
* .subscribeAlways { event: BotEvent ->
* // 现在 event 是 BotEvent, 且 bot.id == 123456
* }
* ```
*
* ## 过滤器挂起
* [filter] 允许挂起协程. **过滤器的挂起将被认为是事件监听器的挂起**.
*
* 过滤器挂起是否会影响事件处理,
* 取决于 [subscribe] 时的 [ConcurrencyKind] [EventPriority].
*
* ## 过滤器异常处理
* [filter] 抛出异常, 将被包装为 [ExceptionInEventChannelFilterException] 并重新抛出.
*
* @see filterIsInstance 过滤指定类型的事件
*/
public actual fun filter(filter: suspend (event: BaseEvent) -> Boolean): EventChannel<BaseEvent> {
return FilterEventChannel(this, filter)
}
/**
* [EventChannel.filter] Java 版本.
*
* 添加一个过滤器. 过滤器将在收到任何事件之后, 传递给通过 [EventChannel.subscribe] 注册的监听器之前调用.
*
* [filter] 返回 `true`, 该事件将会被传给监听器. 否则将会被忽略, **监听器继续监听**.
*
* ## 线性顺序
* 多个 [filter] 的处理是线性且有顺序的. 若一个 [filter] 已经返回了 `false` (代表忽略这个事件), 则会立即忽略, 而不会传递给后续过滤器.
*
* 示例:
* ```
* GlobalEventChannel // GlobalEventChannel 会收到全局所有事件, 事件类型是 Event
* .filterIsInstance(BotEvent.class) // 过滤, 只接受 BotEvent
* .filter(event ->
* // 此时的 event 一定是 BotEvent
* event.bot.id == 123456 // 再过滤 event 的 bot.id
* )
* .subscribeAlways(event -> {
* // 现在 event 是 BotEvent, 且 bot.id == 123456
* })
* ```
*
* ## 过滤器阻塞
* [filter] 允许阻塞线程. **过滤器的阻塞将被认为是事件监听器的阻塞**.
*
* 过滤器阻塞是否会影响事件处理,
* 取决于 [subscribe] 时的 [ConcurrencyKind] [EventPriority].
*
* ## 过滤器异常处理
* [filter] 抛出异常, 将被包装为 [ExceptionInEventChannelFilterException] 并重新抛出.
*
* @see filterIsInstance 过滤指定类型的事件
*
* @since 2.2
*/
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.LowPriorityInOverloadResolution
public actual fun filter(filter: (event: BaseEvent) -> Boolean): EventChannel<BaseEvent> {
return filter { runBIO { filter(it) } }
}
/**
* 过滤事件的类型. 返回一个只包含 [E] 类型事件的 [EventChannel]
* @see filter 获取更多信息
*/
public actual inline fun <reified E : Event> filterIsInstance(): EventChannel<E> =
filterIsInstance(E::class)
/**
* 过滤事件的类型. 返回一个只包含 [E] 类型事件的 [EventChannel]
* @see filter 获取更多信息
*/
public actual fun <E : Event> filterIsInstance(kClass: KClass<out E>): EventChannel<E> {
return filter { kClass.isInstance(it) }.cast()
}
/**
* 创建一个新的 [EventChannel], [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [coroutineContexts].
* [coroutineContexts] 会覆盖 [defaultCoroutineContext] 中的重复元素.
*
* 此操作不会修改 [`this.coroutineContext`][defaultCoroutineContext], 只会创建一个新的 [EventChannel].
*/
public actual abstract fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent>
/**
* 创建一个新的 [EventChannel], [EventChannel] 包含 [this.coroutineContext][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler]
* @see context
*/
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.LowPriorityInOverloadResolution
public actual fun exceptionHandler(coroutineExceptionHandler: CoroutineExceptionHandler): EventChannel<BaseEvent> {
return context(coroutineExceptionHandler)
}
/**
* 创建一个新的 [EventChannel], [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler]
* @see context
*/
public actual fun exceptionHandler(coroutineExceptionHandler: (exception: Throwable) -> Unit): EventChannel<BaseEvent> {
return context(CoroutineExceptionHandler { _, throwable ->
coroutineExceptionHandler(throwable)
})
}
/**
* [coroutineScope] 作为这个 [EventChannel] 的父作用域.
*
* 实际作用为创建一个新的 [EventChannel],
* [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [CoroutineScope.coroutineContext],
* 并以 [CoroutineScope] [Job] (如果有) [作为父 Job][parentJob]
*
* @see parentJob
* @see context
*
* @see CoroutineScope.globalEventChannel `GlobalEventChannel.parentScope()` 的扩展
*/
public actual fun parentScope(coroutineScope: CoroutineScope): EventChannel<BaseEvent> {
return context(coroutineScope.coroutineContext)
}
/**
* 指定协程父 [Job]. 之后在此 [EventChannel] 下创建的事件监听器都会成为 [job] 的子任务, [job] 被取消时, 所有的事件监听器都会被取消.
*
* 注意: 监听器不会失败 ([Job.cancel]). 监听器处理过程的异常都会被捕获然后交由 [CoroutineExceptionHandler] 处理, 因此 [job] 不会因为子任务监听器的失败而被取消.
*
* @see parentScope
* @see context
*/
public actual fun parentJob(job: Job): EventChannel<BaseEvent> {
return context(job)
}
// endregion
// region subscribe
/**
* 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件.
*
* 每当 [事件广播][Event.broadcast] , [handler] 都会被执行.
*
*
* ## 创建监听
* 调用本函数:
* ```
* eventChannel.subscribe<E> { /* 会收到此通道中的所有是 E 的事件 */ }
* ```
*
* ## 生命周期
*
* ### 通过协程作用域管理监听器
* 本函数将会创建一个 [Job], 成为 [parentJob] 中的子任务. 可创建一个 [CoroutineScope] 来管理所有的监听器:
* ```
* val scope = CoroutineScope(SupervisorJob())
*
* val scopedChannel = eventChannel.parentScope(scope) // 将协程作用域 scope 附加到这个 EventChannel
*
* scopedChannel.subscribeAlways<MemberJoinEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务
* scopedChannel.subscribeAlways<MemberMuteEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务
*
* scope.cancel() // 停止了协程作用域, 也就取消了两个监听器
* ```
*
* 这个函数返回 [Listener], 它是一个 [CompletableJob]. 它会成为 [parentJob] [parentScope] 的一个 [子任务][Job]
*
* ### 停止监听
* 如果 [handler] 返回 [ListeningStatus.STOPPED] 监听器将被停止.
*
* 也可以通过 [subscribe] 返回值 [Listener] [Listener.complete]
*
* ## 监听器调度
* 监听器会被创建一个协程任务, 语义上在 [parentScope] 下运行.
* 通过 Kotlin [默认协程调度器][Dispatchers.Default] 在固定的全局共享线程池里执行, 除非有 [coroutineContext] 指定.
*
* 默认在 [handler] 中不能处理阻塞任务. 阻塞任务将会阻塞一个 Kotlin 全局协程调度线程并可能导致严重问题.
* 请通过 `withContext(Dispatchers.IO) { }` 等方法执行阻塞工作.
*
* ## 异常处理
*
* **监听过程抛出的异常是需要尽可能避免的, 因为这将产生不确定性.**
*
* 当参数 [handler] 处理事件抛出异常时, 只会从监听方协程上下文 ([CoroutineContext]) 寻找 [CoroutineExceptionHandler] 处理异常, 即如下顺序:
* 1. 本函数参数 [coroutineContext]
* 2. [EventChannel.defaultCoroutineContext]
* 3. 若以上步骤无法获取 [CoroutineExceptionHandler], 则只会在日志记录异常.
* 因此建议先指定 [CoroutineExceptionHandler] (可通过 [EventChannel.exceptionHandler]) 再监听事件, 或者在监听事件中捕获异常.
*
* 因此, 广播方 ([Event.broadcast]) 不会知晓监听方产生的异常, [Event.broadcast] 过程也不会因监听方产生异常而提前结束.
*
* ***备注***: 2.11 以前, 发生上述异常时还会从广播方和有关 [Bot] 协程域获取 [CoroutineExceptionHandler]. 因此行为不稳定而在 2.11 变更为上述过程.
*
* 事件处理时抛出异常不会停止监听器.
*
* 建议在事件处理中 ( [handler] ) 处理异常,
* 或在参数 [coroutineContext] 中添加 [CoroutineExceptionHandler], 或通过 [EventChannel.exceptionHandler].
*
* ## 并发安全性
* 基于 [concurrency] 参数, 事件监听器可以被允许并行执行.
*
* - [concurrency] [ConcurrencyKind.CONCURRENT], [handler] 可能被并行调用, 需要保证并发安全.
* - [concurrency] [ConcurrencyKind.LOCKED], [handler] 会被 [Mutex] 限制, 串行异步执行.
*
* ## 衍生监听方法
*
* 这些方法仅 Kotlin 可用.
*
* - [syncFromEvent]: 挂起当前协程, 监听一个事件, 并尝试从这个事件中**获取**一个值
* - [nextEvent]: 挂起当前协程, 直到监听到特定类型事件的广播并通过过滤器, 返回这个事件实例.
*
* @param coroutineContext [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext].
* @param concurrency 并发类型. 查看 [ConcurrencyKind]
* @param priority 监听优先级优先级越高越先执行
* @param handler 事件处理器. 在接收到事件时会调用这个处理器. 其返回值意义参考 [ListeningStatus]. 其异常处理参考上文
*
* @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler]
*
*
* @see selectMessages `when` 的语法 '选择' 即将到来的一条消息.
* @see whileSelectMessages `when` 的语法 '选择' 即将到来的所有消息, 直到不满足筛选结果.
*
* @see subscribeAlways 一直监听
* @see subscribeOnce 只监听一次
*
* @see subscribeMessages 监听消息 DSL
*/
public actual inline fun <reified E : Event> subscribe(
coroutineContext: CoroutineContext,
concurrency: ConcurrencyKind,
priority: EventPriority,
noinline handler: suspend E.(E) -> ListeningStatus,
): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority, handler)
/**
* [subscribe] 的区别是接受 [eventClass] 参数, 而不使用 `reified` 泛型. 通常推荐使用具体化类型参数.
*
* @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler]
* @see subscribe
*/
public actual fun <E : Event> subscribe(
eventClass: KClass<out E>,
coroutineContext: CoroutineContext,
concurrency: ConcurrencyKind,
priority: EventPriority,
handler: suspend E.(E) -> ListeningStatus,
): Listener<E> = subscribeInternal(
eventClass,
createListener0(coroutineContext, concurrency, priority) { it.handler(it); }
)
/**
* 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] , [handler] 都会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
*
* @param concurrency 并发类型默认为 [CONCURRENT]
* @param coroutineContext [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext]
* @param priority 处理优先级, 优先级高的先执行
*
* @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler]
*
* @see subscribe 获取更多说明
*/
public actual inline fun <reified E : Event> subscribeAlways(
coroutineContext: CoroutineContext,
concurrency: ConcurrencyKind,
priority: EventPriority,
noinline handler: suspend E.(E) -> Unit,
): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority, handler)
/**
* @see subscribe
* @see subscribeAlways
*/
public actual fun <E : Event> subscribeAlways(
eventClass: KClass<out E>,
coroutineContext: CoroutineContext,
concurrency: ConcurrencyKind,
priority: EventPriority,
handler: suspend E.(E) -> Unit,
): Listener<E> = subscribeInternal(
eventClass,
createListener0(coroutineContext, concurrency, priority) { it.handler(it); ListeningStatus.LISTENING }
)
/**
* 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件, 只监听一次.
* [事件广播][Event.broadcast] , [handler] 会被执行.
*
* 可在任意时候通过 [Listener.complete] 来主动停止监听.
*
* @param coroutineContext [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext]
* @param priority 处理优先级, 优先级高的先执行
*
* @see subscribe 获取更多说明
*/
public actual inline fun <reified E : Event> subscribeOnce(
coroutineContext: CoroutineContext,
priority: EventPriority,
noinline handler: suspend E.(E) -> Unit,
): Listener<E> = subscribeOnce(E::class, coroutineContext, priority, handler)
/**
* @see subscribeOnce
*/
public actual fun <E : Event> subscribeOnce(
eventClass: KClass<out E>,
coroutineContext: CoroutineContext,
priority: EventPriority,
handler: suspend E.(E) -> Unit,
): Listener<E> = subscribeInternal(
eventClass,
createListener0(coroutineContext, ConcurrencyKind.LOCKED, priority) { it.handler(it); ListeningStatus.STOPPED }
)
// endregion
// region impl
// protected, to hide from users
@MiraiInternalApi
protected actual abstract fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>)
// to overcome visibility issue
@OptIn(MiraiInternalApi::class)
internal actual fun <E : Event> registerListener0(eventClass: KClass<out E>, listener: Listener<E>) {
return registerListener(eventClass, listener)
}
private fun <L : Listener<E>, E : Event> subscribeInternal(eventClass: KClass<out E>, listener: L): L {
registerListener0(eventClass, listener)
return listener
}
/**
* Creates [Listener] instance using the [listenerBlock] action.
*/
// @Contract("_ -> new") // always creates new instance
@MiraiInternalApi
protected actual abstract fun <E : Event> createListener(
coroutineContext: CoroutineContext,
concurrencyKind: ConcurrencyKind,
priority: EventPriority,
listenerBlock: suspend (E) -> ListeningStatus,
): Listener<E>
// to overcome visibility issue
@OptIn(MiraiInternalApi::class)
internal actual fun <E : Event> createListener0(
coroutineContext: CoroutineContext,
concurrencyKind: ConcurrencyKind,
priority: EventPriority,
listenerBlock: suspend (E) -> ListeningStatus,
): Listener<E> = createListener(coroutineContext, concurrencyKind, priority, listenerBlock)
// endregion
}

View File

@ -1,27 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.event
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.utils.MiraiInternalApi
/**
* [selectMessagesUnit] [selectMessages] 时的 DSL 构建器.
*
* 它是特殊化的消息监听 ([EventChannel.subscribeMessages]) DSL
*
* @see MessageSubscribersBuilder 查看上层 API
*/
@OptIn(MiraiInternalApi::class)
public actual abstract class MessageSelectBuilderUnit<M : MessageEvent, R> @PublishedApi internal actual constructor(
ownerMessagePacket: M,
stub: Any?,
subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
) : CommonMessageSelectBuilderUnit<M, R>(ownerMessagePacket, stub, subscriber)

View File

@ -1,21 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.serialization.KSerializer
import kotlinx.serialization.modules.SerializersModule
import kotlin.reflect.KClass
internal actual fun <M : Any> SerializersModule.overwritePolymorphicWith(
type: KClass<M>,
serializer: KSerializer<M>
): SerializersModule {
throw UnsupportedOperationException("overwritePolymorphicWith is not supported on native.")
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils
import io.ktor.utils.io.core.*
import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.utils.*
internal class ExternalResourceImplByByteArray(
private val data: ByteArray,
formatName: String?
) : ExternalResource {
override val size: Long = data.size.toLong()
override val md5: ByteArray by lazy { data.md5() }
override val sha1: ByteArray by lazy { data.sha1() }
override val formatName: String by lazy {
formatName ?: getFileType(data.copyOf(COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE))
?: ExternalResource.DEFAULT_FORMAT_NAME
}
override val closed: CompletableDeferred<Unit> = CompletableDeferred()
override val origin: Any
get() = data//.clone()
@MiraiInternalApi
override fun input(): Input = ByteReadPacket(data)
override fun close() {
kotlin.runCatching { closed.complete(Unit) }
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils
internal actual interface Marker {
actual fun addParents(vararg parent: Marker)
}
internal class MarkerImpl(
private val name: String
) : Marker {
override fun addParents(vararg parent: Marker) {
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.utils
internal actual object MarkerManager {
actual fun getMarker(name: String): Marker {
return MarkerImpl(name)
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.action
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.MessageSource.Key.recallIn
/**
* 异步撤回结果.
*
* 可由 [MessageSource.recallIn] 返回得到.
*
* ## 用法
*
* ### 获取撤回失败时的异常
*
* ```
* val exception = result.exception.await() // 挂起协程并等待撤回的结果.
* if (exception == null) {
* // 撤回成功
* } else {
* // 撤回失败
* }
* ```
*
* 若仅需要了解撤回是否成功而不需要获取详细异常实例, 可使用 [isSuccess]
*
* @see MessageSource.recallIn
*/
public actual class AsyncRecallResult internal actual constructor(
/**
* 撤回时产生的异常, 当撤回成功时为 `null`.
*/
public actual val exception: Deferred<Throwable?>,
) {
/**
* 撤回是否成功.
*/
public actual val isSuccess: Deferred<Boolean> by lazy {
CompletableDeferred<Boolean>().apply {
exception.invokeOnCompletion {
complete(it == null)
}
}
}
/**
* 挂起协程直到撤回完成, 返回撤回时产生的异常. 当撤回成功时返回 `null`.
*/
public actual suspend fun awaitException(): Throwable? {
return exception.await()
}
/**
* 挂起协程直到撤回完成, 返回撤回的结果.
*/
public actual suspend fun awaitIsSuccess(): Boolean {
return isSuccess.await()
}
}

View File

@ -1,110 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.FileSupported
import net.mamoe.mirai.contact.file.AbsoluteFile
import net.mamoe.mirai.event.events.MessageEvent
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
import net.mamoe.mirai.message.data.visitor.MessageVisitor
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.safeCast
/**
* 文件消息.
*
* [name] [size] 只供本地使用, 发送消息时只会使用 [id] [internalId].
*
* : [FileMessage] 不可二次发送
*
* ### 文件操作
* 要下载这个文件, 可通过 [toAbsoluteFile] 获取到 [AbsoluteFile] 然后操作.
*
* 要获取到 [FileMessage], 可以通过 [MessageEvent.message] 获取, 或通过 [AbsoluteFile.toMessage] 得到.
*
* @since 2.5
* @suppress [FileMessage] 的使用是稳定的, 但自行实现不稳定.
*/
@Serializable(FileMessage.Serializer::class)
@SerialName(FileMessage.SERIAL_NAME)
@NotStableForInheritance
public actual interface FileMessage : MessageContent, ConstrainSingle, CodableMessage {
/**
* 服务器需要的某种 ID.
*/
public actual val id: String
/**
* 服务器需要的某种 ID.
*/
public actual val internalId: Int
/**
* 文件名
*/
public actual val name: String
/**
* 文件大小 bytes
*/
public actual val size: Long
actual override fun contentToString(): String = "[文件]$name" // orthodox
@MiraiExperimentalApi
actual override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:file:")
builder.appendStringAsMiraiCode(id).append(",")
builder.append(internalId).append(",")
builder.appendStringAsMiraiCode(name).append(",")
builder.append(size).append("]")
}
/**
* 获取一个对应的 [AbsoluteFile]. 当目标群或好友不存在这个文件时返回 `null`.
*
* @since 2.8
*/
public actual suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile?
actual override val key: Key get() = Key
@MiraiInternalApi
actual override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
return visitor.visitFileMessage(this, data)
}
/**
* 注意, baseKey [MessageContent] 不稳定. 未来可能会有变更.
*/
public actual companion object Key :
AbstractPolymorphicMessageKey<MessageContent, FileMessage>(
MessageContent, { it.safeCast() }) {
public actual const val SERIAL_NAME: String = "FileMessage"
/**
* 构造 [FileMessage]
* @since 2.5
*/
public actual fun create(id: String, internalId: Int, name: String, size: Long): FileMessage =
Mirai.createFileMessage(id, internalId, name, size)
}
public actual object Serializer :
KSerializer<FileMessage> by @OptIn(MiraiInternalApi::class) FallbackFileMessageSerializer()
}

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai

View File

@ -1,73 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
import net.mamoe.mirai.Bot
/**
* [BotConfiguration] Native 平台特别配置
* @since 2.15
*/
@NotStableForInheritance
public actual abstract class AbstractBotConfiguration { // open for Java
protected actual abstract var deviceInfo: ((Bot) -> DeviceInfo)?
protected actual abstract var networkLoggerSupplier: ((Bot) -> MiraiLogger)
protected actual abstract var botLoggerSupplier: ((Bot) -> MiraiLogger)
/**
* 工作目录. 默认为当前目录
*/
public var workingDir: String = "."
/**
* 使用文件存储设备信息.
*
* 此函数只在 JVM Android 有效. 在其他平台将会抛出异常.
* @param filepath 文件路径. 默认是相对于 [workingDir] 的文件 "device.json".
* @see deviceInfo
*/
@BotConfiguration.ConfigurationDsl
public actual fun fileBasedDeviceInfo(filepath: String) {
deviceInfo = {
val file = MiraiFile.create(workingDir).resolve(filepath)
if (!file.exists()) {
file.writeText(DeviceInfoManager.serialize(DeviceInfo.random(), BotConfiguration.json))
}
DeviceInfoManager.deserialize(file.readText(), BotConfiguration.json) {
file.writeText(DeviceInfoManager.serialize(it, BotConfiguration.json))
}
}
}
/**
* 缓存数据目录路径. [cacheDir] 为绝对路径, 将解析该绝对路径, 否则作为相对于 [workingDir] 的路径解析.
* 例如, `cache` 将会解析为 `$workingDir/cache`, `/Users/Chisato/Desktop/bot/cache` 指代绝对路径, 将解析为绝对路径.
*
* 缓存目录保存的内容均属于不稳定的 Mirai 内部数据, 请不要手动修改它们. 清空缓存不会影响功能. 只会导致一些操作如读取全部群列表要重新进行.
* 默认启用的缓存可以加快登录过程.
*
* 注意: 这个目录只存储能在 [BotConfiguration] 配置的内容, 即包含:
* - 联系人列表
* - 登录服务器列表
* - 资源服务秘钥
*
* 其他内容如通过 [Input] 发送图片时的缓存使用 [FileCacheStrategy], 默认使用系统临时文件且会在关闭时删除文件.
*
* @since 2.4
*/
public var cacheDir: String = "cache"
internal actual fun applyMppCopy(new: BotConfiguration) {
new.workingDir = workingDir
new.cacheDir = cacheDir
}
}

View File

@ -1,144 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlin.random.Random
/**
* 表示设备信息
* @see DeviceInfoBuilder
*/
@Serializable(DeviceInfoV1LegacySerializer::class)
public actual class DeviceInfo
@Deprecated(DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING)
@DeprecatedSinceMirai(warningSince = "2.15") // planned internal
public actual constructor(
public actual val display: ByteArray,
public actual val product: ByteArray,
public actual val device: ByteArray,
public actual val board: ByteArray,
public actual val brand: ByteArray,
public actual val model: ByteArray,
public actual val bootloader: ByteArray,
public actual val fingerprint: ByteArray,
public actual val bootId: ByteArray,
public actual val procVersion: ByteArray,
public actual val baseBand: ByteArray,
public actual val version: Version,
public actual val simInfo: ByteArray,
public actual val osType: ByteArray,
public actual val macAddress: ByteArray,
public actual val wifiBSSID: ByteArray,
public actual val wifiSSID: ByteArray,
public actual val imsiMd5: ByteArray,
public actual val imei: String,
public actual val apn: ByteArray,
public actual val androidId: ByteArray,
) {
public actual val ipAddress: ByteArray get() = byteArrayOf(192.toByte(), 168.toByte(), 1, 123)
init {
require(imsiMd5.size == 16) { "Bad `imsiMd5.size`. Required 16, given ${imsiMd5.size}." }
}
@Transient
@MiraiInternalApi
public actual val guid: ByteArray = generateGuid(androidId, macAddress)
@Serializable
public actual class Version actual constructor(
public actual val incremental: ByteArray,
public actual val release: ByteArray,
public actual val codename: ByteArray,
public actual val sdk: Int
) {
/**
* @since 2.9
*/
actual override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Version) return false
if (!incremental.contentEquals(other.incremental)) return false
if (!release.contentEquals(other.release)) return false
if (!codename.contentEquals(other.codename)) return false
if (sdk != other.sdk) return false
return true
}
/**
* @since 2.9
*/
actual override fun hashCode(): Int {
var result = incremental.contentHashCode()
result = 31 * result + release.contentHashCode()
result = 31 * result + codename.contentHashCode()
result = 31 * result + sdk
return result
}
}
public actual companion object {
internal actual val logger = MiraiLogger.Factory.create(DeviceInfo::class, "DeviceInfo")
/**
* 生成随机 [DeviceInfo]
*
* @see DeviceInfoBuilder
* @since 2.0
*/
public actual fun random(): DeviceInfo = random(Random.Default)
/**
* 使用特定随机数生成器生成 [DeviceInfo]
*
* @see DeviceInfoBuilder
* @since 2.9
*/
public actual fun random(random: Random): DeviceInfo {
return DeviceInfoCommonImpl.randomDeviceInfo(random)
}
/**
* 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo].
*
* 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用.
*
* @since 2.15
*/
public actual fun serializeToString(deviceInfo: DeviceInfo): String = DeviceInfoManager.serialize(deviceInfo)
/**
* 将通过 [serializeToString] 序列化得到的字符串反序列化为 [DeviceInfo].
* 此函数兼容旧版 mirai 序列化的字符串.
* @since 2.15
*/
public actual fun deserializeFromString(string: String): DeviceInfo = DeviceInfoManager.deserialize(string)
}
/**
* @since 2.9
*/
@Suppress("DuplicatedCode")
actual override fun equals(other: Any?): Boolean {
return DeviceInfoCommonImpl.equalsImpl(this, other)
}
/**
* @since 2.9
*/
actual override fun hashCode(): Int {
return DeviceInfoCommonImpl.hashCodeImpl(this)
}
}

View File

@ -1,273 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Deferred
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Contact.Companion.sendImage
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
import net.mamoe.mirai.internal.utils.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage
/**
* 一个*不可变的*外部资源. 仅包含资源内容, 大小, 文件类型, 校验值而不包含文件名, 文件位置等. 外部资源有可能是一个文件, 也有可能只存在于内存, 或者以任意其他方式实现.
*
* [ExternalResource] 在创建之后就应该保持其属性的不变, 即任何时候获取其属性都应该得到相同结果, 任何时候打开流都得到的一样的数据.
*
* # 创建
* - [ByteArray.toExternalResource]
*
* ## Kotlin 获得和使用 [ExternalResource] 实例
*
* ```
* file.toExternalResource().use { resource -> // 安全地使用资源
* contact.uploadImage(resource) // 用来上传图片
* contact.files.uploadNewFile("/foo/test.txt", file) // 或者用来上传文件
* }
* ```
*
* 注意, 若使用 [Input], 必须手动关闭 [Input]. 一种使用情况示例:
*
* ```
* inputStream.use { input -> // 安全地使用 InputStream
* input.toExternalResource().use { resource -> // 安全地使用资源
* contact.uploadImage(resource) // 用来上传图片
* contact.files.uploadNewFile("/foo/test.txt", file) // 或者用来上传文件
* }
* }
* ```
*
* ## Java 获得和使用 [ExternalResource] 实例
*
* ```
* try (ExternalResource resource = ExternalResource.create(file)) { // 使用文件 file
* contact.uploadImage(resource); // 用来上传图片
* contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件
* }
* ```
*
* 注意, 若使用 [Input], 必须手动关闭 [Input]. 一种使用情况示例:
*
* ```java
* try (InputStream stream = ...) { // 安全地使用 InputStream
* try (ExternalResource resource = ExternalResource.create(stream)) { // 安全地使用资源
* contact.uploadImage(resource); // 用来上传图片
* contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件
* }
* }
* ```
*
* # 释放
*
* [ExternalResource] 创建时就可能会打开一些资源.
* 类似于 [Input], [ExternalResource] 需要被 [关闭][close].
*
* ## 未释放资源的补救策略
*
* 2.7 , 每个 mirai 内置的 [ExternalResource] 实现都有引用跟踪, [ExternalResource] GC 后会执行被动释放.
* 这依赖于 JVM 垃圾收集策略, 因此不可靠, 资源仍然需要手动 close.
*
* ## 使用单次自动释放
*
* 若创建的资源仅需要*很快地*使用一次, 可使用 [toAutoCloseable] 获得在使用一次后就会自动关闭的资源.
*
* 示例:
* ```java
* contact.uploadImage(ExternalResource.create(file).toAutoCloseable()); // 创建并立即使用单次自动释放的资源
* ```
*
* **注意**: 如果仅使用 [toAutoCloseable] 而不通过 [Contact.uploadImage] mirai 内置方法使用资源, 资源仍然会处于打开状态且不会被自动关闭.
* 最终资源会由上述*未释放资源的补救策略*关闭, 但这依赖于 JVM 垃圾收集策略而不可靠.
* 因此建议在创建单次自动释放的资源后就尽快使用它, 否则仍然需要考虑在正确的时间及时关闭资源.
*
* # 实现 [ExternalResource]
*
* 可以自行实现 [ExternalResource]. 但通常上述创建方法已足够使用.
*
* 建议继承 [AbstractExternalResource], 这将支持上文提到的资源自动释放功能.
*
* 实现时需保持 [ExternalResource] 在构造后就不可变, 并且所有属性都总是返回一个固定值.
*
* @see ExternalResource.uploadAsImage 将资源作为图片上传, 得到 [Image]
* @see ExternalResource.sendAsImageTo 将资源作为图片发送
* @see Contact.uploadImage 上传一个资源作为图片, 得到 [Image]
* @see Contact.sendImage 发送一个资源作为图片
*
* @see FileCacheStrategy
*/
public actual interface ExternalResource : Closeable {
/**
* 是否在 _使用一次_ 后自动 [close].
*
* 该属性仅供调用方参考. [Contact.uploadImage] 会在方法结束时关闭 [isAutoClose] `true` [ExternalResource], 无论上传图片是否成功.
*
* 所有 mirai 内置的上传图片, 上传语音等方法都支持该行为.
*
* @since 2.8
*/
public actual val isAutoClose: Boolean
get() = false
/**
* 文件内容 MD5. 16 bytes
*/
public actual val md5: ByteArray
/**
* 文件内容 SHA1. 16 bytes
* @since 2.5
*/
public actual val sha1: ByteArray
get() =
throw UnsupportedOperationException("ExternalResource.sha1 is not implemented by ${this::class.simpleName}")
// 如果你要实现 [ExternalResource], 你也应该实现 [sha1].
// 这里默认抛出 [UnsupportedOperationException] 是为了 (姑且) 兼容 2.5 以前的版本的实现.
/**
* 文件格式 "png", "amr". 当无法自动识别格式时为 [DEFAULT_FORMAT_NAME].
*
* 默认会从文件头识别, 支持的文件类型:
* png, jpg, gif, tif, bmp, amr, silk
*
* @see net.mamoe.mirai.utils.getFileType
* @see net.mamoe.mirai.utils.FILE_TYPES
* @see DEFAULT_FORMAT_NAME
*/
public actual val formatName: String
/**
* 文件大小 bytes
*/
public actual val size: Long
/**
* [close] 时会 [CompletableDeferred.complete] [Deferred].
*/
public actual val closed: Deferred<Unit>
/**
* 打开 [Input]. 在返回的 [Input] [关闭][Input.close] 前无法再次打开流.
*
* 关闭此流不会关闭 [ExternalResource].
* @throws IllegalStateException 当上一个流未关闭又尝试打开新的流时抛出
*
* @since 2.13
*/
@MiraiInternalApi
public actual fun input(): Input
@MiraiInternalApi
public actual fun calculateResourceId(): String {
return generateImageId(md5, formatName.ifEmpty { DEFAULT_FORMAT_NAME })
}
/**
* [ExternalResource] 的数据来源, 可能有以下的返回
*
* - [ByteArray] RAM
* - ...
*
* implementation note:
*
* - 对于无法二次读取的数据来源 ( [Input]), 返回 `null`
* - 不要返回 [String], 没有约定 [String] 代表什么
* - 数据源外漏会严重影响 [inputStream] 等的执行的可以返回 `null` (如文件句柄)
*
* @since 2.8.0
*/
public actual val origin: Any? get() = null
/**
* 创建一个在 _使用一次_ 后就会自动 [close] [ExternalResource].
*
* @since 2.8.0
*/
public actual fun toAutoCloseable(): ExternalResource {
return if (isAutoClose) this else {
val delegate = this
object : ExternalResource by delegate {
override val isAutoClose: Boolean get() = true
override fun toString(): String = "ExternalResourceWithAutoClose(delegate=$delegate)"
override fun toAutoCloseable(): ExternalResource {
return this
}
}
}
}
public actual companion object {
/**
* 在无法识别文件格式时使用的默认格式名. "mirai".
*
* @see ExternalResource.formatName
*/
public actual const val DEFAULT_FORMAT_NAME: String = "mirai"
///////////////////////////////////////////////////////////////////////////
// region toExternalResource
///////////////////////////////////////////////////////////////////////////
/**
* 创建 [ExternalResource]. 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭.
*
* @param formatName 查看 [ExternalResource.formatName]
*/
public actual fun ByteArray.toExternalResource(formatName: String?): ExternalResource =
ExternalResourceImplByByteArray(this, formatName)
// endregion
///////////////////////////////////////////////////////////////////////////
// region sendAsImageTo
///////////////////////////////////////////////////////////////////////////
/**
* 将图片作为单独的消息发送给指定联系人.
*
* **注意**本函数不会关闭 [ExternalResource].
*
* @see Contact.uploadImage 上传图片
* @see Contact.sendMessage 最终调用, 发送消息.
*
* @throws OverFileSizeMaxException
*/
public actual suspend fun <C : Contact> ExternalResource.sendAsImageTo(contact: C): MessageReceipt<C> =
contact.uploadImage(this).sendTo(contact)
// endregion
///////////////////////////////////////////////////////////////////////////
// region uploadAsImage
///////////////////////////////////////////////////////////////////////////
/**
* 上传图片并构造 [Image]. 这个函数可能需消耗一段时间.
*
* **注意**本函数不会关闭 [ExternalResource].
*
* @param contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人.
*
* @see Contact.uploadImage 最终调用, 上传图片.
*/
public actual suspend fun ExternalResource.uploadAsImage(contact: Contact): Image = contact.uploadImage(this)
// endregion
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
/**
* 资源缓存策略.
*
* 注意: 本接口只用于 JVM 平台. native 平台没有作用.
*
* 由于上传资源时服务器要求提前给出 MD5 和文件大小等数据, 一些资源如 [InputStream] 需要首先缓存才能使用.
*
* 资源的缓存都是将 [InputStream] 缓存未 [ExternalResource]. 根据 [FileCacheStrategy] 实现不同, 可以以临时文件存储, 也可以在数据库或是内存按需存储.
* Mirai 内置的实现有 [内存存储][MemoryCache] [临时文件存储][TempCache].
* 操作 [ExternalResource.toExternalResource] 时将会使用 [IMirai.FileCacheStrategy]. 可以覆盖, 示例:
* ```
* // Kotlin
* Mirai.FileCacheStrategy = FileCacheStrategy.TempCache() // 使用系统默认缓存路径, 也是默认的行为
* Mirai.FileCacheStrategy = FileCacheStrategy.TempCache(File("C:/cache")) // 使用自定义缓存路径
*
* // Java
* Mirai.getInstance().setFileCacheStrategy(new FileCacheStrategy.TempCache()); // 使用系统默认缓存路径, 也是默认的行为
* Mirai.getInstance().setFileCacheStrategy(new FileCacheStrategy.TempCache(new File("C:/cache"))); // 使用自定义的缓存路径
* ```
*
* 此接口的实现和使用都是稳定的. 自行实现的 [FileCacheStrategy] 也可以被 Mirai 使用.
*
* 注意, 此接口目前仅缓存 [InputStream] 等一次性数据. 好友列表等数据由每个 [Bot] [BotConfiguration.cacheDir] 缓存.
*
* ### 使用 [FileCacheStrategy] 的操作
* - [ExternalResource.toExternalResource]
* - [ExternalResource.uploadAsImage]
* - [ExternalResource.sendAsImageTo]
*
* @see ExternalResource
*/
public actual interface FileCacheStrategy {
public actual companion object {
/**
* 当前平台下默认的缓存策略. 注意, 这可能不是 Mirai 全局默认使用的, Mirai [IMirai.FileCacheStrategy] 获取.
*
* @see IMirai.FileCacheStrategy
*/
@MiraiExperimentalApi
public actual val PlatformDefault: FileCacheStrategy = object : FileCacheStrategy {}
}
}

View File

@ -1,15 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
internal actual object PlatformLoginSolverImplementations {
actual val isSliderCaptchaSupported: Boolean get() = false
actual val default: LoginSolver? get() = null
}

View File

@ -1,188 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.reflect.KClass
/**
* 日志记录器.
*
* ## Mirai 日志系统
*
* Mirai 内建简单的日志系统, [MiraiLogger]. [MiraiLogger] 的实现有 [SimpleLogger], [PlatformLogger], [SilentLogger].
*
* ## 实现或使用 [MiraiLogger]
*
* 不建议实现或使用 [MiraiLogger]. 请优先考虑使用上述第三方框架. [MiraiLogger] 仅应用于兼容旧版本代码.
*
* @see SimpleLogger 简易 logger, 它将所有的日志记录操作都转移给 lambda `(String?, Throwable?) -> Unit`
* @see PlatformLogger 各个平台下的默认日志记录实现.
* @see SilentLogger 忽略任何日志记录操作的 logger 实例.
*
* @see MiraiLoggerPlatformBase 平台通用基础实现. Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数.
*/
public actual interface MiraiLogger {
/**
* 可以 service 实现的方式覆盖.
*
* @since 2.7
*/
public actual interface Factory {
/**
* 创建 [MiraiLogger] 实例.
*
* @param requester 请求创建 [MiraiLogger] 的对象的 class
* @param identity 对象标记 (备注)
*/
public actual fun create(requester: KClass<*>, identity: String?): MiraiLogger {
return create(requester)
}
/**
* 创建 [MiraiLogger] 实例.
*
* @param requester 请求创建 [MiraiLogger] 的对象
*/
public actual fun create(requester: KClass<*>): MiraiLogger {
throw UnsupportedOperationException() // Cannot be abstract since on JVM it is open
}
public actual companion object INSTANCE : Factory by loadService(Factory::class, fallbackImplementation = {
@OptIn(MiraiInternalApi::class)
object : Factory {
override fun create(requester: KClass<*>): MiraiLogger {
return PlatformLogger(requester.simpleName ?: requester.qualifiedName)
}
override fun create(requester: KClass<*>, identity: String?): MiraiLogger {
return PlatformLogger(identity ?: requester.simpleName ?: requester.qualifiedName)
}
}
})
}
public actual companion object;
/**
* 日志的标记. Mirai , identity 可为
* - "Bot"
* - "BotNetworkHandler"
* .
*
* 它只用于帮助调试或统计. 十分建议清晰定义 identity
*/
public actual val identity: String?
/**
* 获取 [MiraiLogger] 是否已开启
*
* [MiraiLoggerWithSwitch] 可控制开关外, 其他的所有 [MiraiLogger] 均一直开启.
*/
public actual val isEnabled: Boolean
/**
* VERBOSE 级别的日志启用时返回 `true`.
*
* [isEnabled] `false`, 返回 `false`.
* 其他情况下返回 [isEnabled] 的值.
*
* @since 2.7
*/
public actual val isVerboseEnabled: Boolean get() = isEnabled
/**
* DEBUG 级别的日志启用时返回 `true`
*
* [isEnabled] `false`, 返回 `false`.
* 其他情况下返回 [isEnabled] 的值.
*
* @since 2.7
*/
public actual val isDebugEnabled: Boolean get() = isEnabled
/**
* INFO 级别的日志启用时返回 `true`
*
* [isEnabled] `false`, 返回 `false`.
* 其他情况下返回 [isEnabled] 的值.
*
* @since 2.7
*/
public actual val isInfoEnabled: Boolean get() = isEnabled
/**
* WARNING 级别的日志启用时返回 `true`
*
* [isEnabled] `false`, 返回 `false`.
* 其他情况下返回 [isEnabled] 的值.
*
* @since 2.7
*/
public actual val isWarningEnabled: Boolean get() = isEnabled
/**
* ERROR 级别的日志启用时返回 `true`
*
* [isEnabled] `false`, 返回 `false`.
* 其他情况下返回 [isEnabled] 的值.
*
* @since 2.7
*/
public actual val isErrorEnabled: Boolean get() = isEnabled
/**
* 记录一个 `verbose` 级别的日志.
* 无关紧要的, 经常大量输出的日志应使用它.
*/
public actual fun verbose(message: String?)
public actual fun verbose(e: Throwable?): Unit = verbose(null, e)
public actual fun verbose(message: String?, e: Throwable?)
/**
* 记录一个 _调试_ 级别的日志.
*/
public actual fun debug(message: String?)
public actual fun debug(e: Throwable?): Unit = debug(null, e)
public actual fun debug(message: String?, e: Throwable?)
/**
* 记录一个 _信息_ 级别的日志.
*/
public actual fun info(message: String?)
public actual fun info(e: Throwable?): Unit = info(null, e)
public actual fun info(message: String?, e: Throwable?)
/**
* 记录一个 _警告_ 级别的日志.
*/
public actual fun warning(message: String?)
public actual fun warning(e: Throwable?): Unit = warning(null, e)
public actual fun warning(message: String?, e: Throwable?)
/**
* 记录一个 _错误_ 级别的日志.
*/
public actual fun error(message: String?)
public actual fun error(e: Throwable?): Unit = error(null, e)
public actual fun error(message: String?, e: Throwable?)
/** 根据优先级调用对应函数 */
public actual fun call(priority: SimpleLogger.LogPriority, message: String?, e: Throwable?): Unit =
@OptIn(MiraiExperimentalApi::class)
priority.correspondingFunction(this, message, e)
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import net.mamoe.mirai.internal.utils.StdoutLogger
/**
* 当前平台的默认的日志记录器.
* - _JVM 控制台_ 端的实现为 [println]
* - _Android_ 端的实现为 `android.util.Log`
*
*
* 单条日志格式 (正则) :
* ```regex
* ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$
* ```
* 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容.
*
* 示例:
* ```log
* 2020-05-21 19:51:09 V/Bot 123456789: Send: OidbSvc.0x88d_7
* ```
*
* 日期时间格式为 `yyyy-MM-dd HH:mm:ss`,
*
* 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error
*
* @see MiraiLogger.create
*/
@MiraiInternalApi
public actual open class PlatformLogger actual constructor(identity: String?) :
MiraiLoggerPlatformBase(), MiraiLogger {
private val delegate = StdoutLogger(identity)
override val identity: String? get() = delegate.identity
override val isEnabled: Boolean get() = delegate.isEnabled
override val isVerboseEnabled: Boolean get() = delegate.isVerboseEnabled
override val isDebugEnabled: Boolean get() = delegate.isDebugEnabled
override val isInfoEnabled: Boolean get() = delegate.isInfoEnabled
override val isWarningEnabled: Boolean get() = delegate.isWarningEnabled
override val isErrorEnabled: Boolean get() = delegate.isErrorEnabled
override fun verbose0(message: String?, e: Throwable?) {
delegate.verbose0(message, e)
}
override fun debug0(message: String?, e: Throwable?) {
delegate.debug(message, e)
}
override fun info0(message: String?, e: Throwable?) {
delegate.info0(message, e)
}
override fun warning0(message: String?, e: Throwable?) {
delegate.warning(message, e)
}
override fun error0(message: String?, e: Throwable?) {
delegate.error0(message, e)
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.toList
import net.mamoe.mirai.contact.announcement.Announcement
import net.mamoe.mirai.contact.announcement.Announcements
/**
* 表示一个可以创建[数据流][Flow]的对象.
*
* 实现这个接口的对象可以看做为元素 [T] 的集合.
* 例如 [Announcements] 可以看作是 [Announcement] 的集合,
* 使用 [Announcements.asFlow] 可以获取到包含所有 [Announcement] 列表的 [Flow]
* JVM, 还可以使用 `Announcements.asStream` 可以获取到包含所有 [Announcement] 列表的 `Stream`.
*
* @since 2.13
*/
public actual interface Streamable<T> {
/**
* 创建一个能获取 [T] [Flow].
*/
public actual fun asFlow(): Flow<T>
/**
* 获取所有 [T] 列表, 将全部 [T] 都加载后再返回.
*
* @return 此时刻的 [T] 只读列表.
*/
public actual suspend fun toList(): List<T> = asFlow().toList()
}

View File

@ -28,7 +28,6 @@ kotlin {
apply(plugin = "explicit-api")
configureJvmTargetsHierarchical("net.mamoe.mirai.utils")
configureNativeTargetsHierarchical(project)
sourceSets {
val commonMain by getting {
@ -42,15 +41,6 @@ kotlin {
relocateImplementation(`ktor-io_relocated`)
}
}
configure(NATIVE_TARGETS.map { getByName(it + "Main") }
+ NATIVE_TARGETS.map { getByName(it + "Test") }) {
dependencies {
// no relocation in native
implementation(`ktor-io`) {
exclude(ExcludeProperties.`slf4j-api`)
}
}
}
val commonTest by getting {
dependencies {
@ -81,12 +71,6 @@ kotlin {
runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE
}
}
findByName("nativeMain")?.apply {
dependencies {
// implementation("com.soywiz.korlibs.krypto:krypto:2.4.12") // ':mirai-core-utils:compileNativeMainKotlinMetadata' fails because compiler cannot find reference
}
}
}
}

View File

@ -1,343 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.bits.*
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import kotlinx.cinterop.*
import platform.posix.*
import platform.windows.*
private fun getFullPathName(path: String): String = memScoped {
ShortArray(MAX_PATH).usePinned { pin ->
val len = GetFullPathNameW(path, MAX_PATH, pin.addressOf(0).reinterpret(), null).toInt()
if (len != 0) {
return pin.get().toKStringFromUtf16(len)
} else {
when (val errno = errno) {
ENOTDIR -> return@memScoped path
EACCES -> return@memScoped path // permission denied
ENOENT -> return@memScoped path // no such file
else -> throw IllegalArgumentException(
"Invalid path($errno): $path",
cause = PosixException.forErrno(posixFunctionName = "GetFullPathNameW()")
)
}
}
}
}
private fun ShortArray.toKStringFromUtf16(len: Int): String {
val chars = CharArray(len)
var index = 0
while (index < len) {
chars[index] = this[index].toInt().toChar()
++index
}
return chars.concatToString()
}
internal actual class MiraiFileImpl actual constructor(
// canonical
path: String
) : MiraiFile {
override val path = path.replace("/", "\\")
actual companion object {
private val ROOT_REGEX = Regex("""^([a-zA-z]+:[/\\])""")
private const val SEPARATOR = '\\'
@Suppress("UnnecessaryOptInAnnotation")
@OptIn(UnsafeNumber::class)
actual fun getWorkingDir(): MiraiFile {
val path = memScoped {
ByteArray(PATH_MAX).usePinned {
getcwd(it.addressOf(0), it.get().size.convert())
it.get().toKString()
}
}
return MiraiFile.create(path)
}
}
override val absolutePath: String by lazy {
val result = ROOT_REGEX.matchEntire(this.path)
?: return@lazy getFullPathName(this.path).removeSuffix(SEPARATOR.toString())
return@lazy result.groups.first()!!.value
}
private fun Char.isSeparator() = this == '/' || this == '\\'
override val parent: MiraiFile? by lazy {
if (ROOT_REGEX.matchEntire(this.path) != null) return@lazy null
val absolute = absolutePath
val p = absolute.substringBeforeLast(SEPARATOR, "")
if (p.isEmpty()) {
return@lazy null
}
if (p.lastOrNull() == ':') {
if (absolute.lastIndexOf(SEPARATOR) == p.lastIndex) {
// file is C:/
return@lazy null
} else {
return@lazy MiraiFileImpl("$p/") // file is C:/xxx
}
}
MiraiFileImpl(p)
}
override val name: String
get() = if (absolutePath.matches(ROOT_REGEX)) absolutePath
else absolutePath.substringAfterLast(SEPARATOR)
init {
checkName(absolutePath.substringAfterLast(SEPARATOR)) // do not check drive letter
}
private fun checkName(name: String) {
name.substringAfterLast(SEPARATOR).forEach { c ->
if (c in """\/:?*"><|""") {
throw IllegalArgumentException("'${name}' contains illegal character '$c'.")
}
}
// memScoped {
// val b = alloc<WINBOOLVar>()
// CheckNameLegalDOS8Dot3A(absolutePath, nullPtr(), 0, nullPtr(), b.ptr)
// if (b.value != 1) {
// throw IllegalArgumentException("'${name}' contains illegal character.")
// }
// }
}
override val length: Long
get() = useStat { it.st_size.convert() } ?: 0
// memScoped {
// val handle = CreateFileW(
// absolutePath,
// GENERIC_READ,
// FILE_SHARE_READ,
// null,
// OPEN_EXISTING,
// FILE_ATTRIBUTE_NORMAL,
// null
// ) ?: return@memScoped 0
// val length = alloc<DWORDVar>()
// if (GetFileSize(handle, length.ptr) == INVALID_FILE_SIZE) {
// if (GetLastError() == NO_ERROR.toUInt()) {
// return INVALID_FILE_SIZE.convert()
// }
// throw PosixException.forErrno(posixFunctionName = "GetFileSize()").wrapIO()
// }
// if (CloseHandle(handle) == FALSE) {
// throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
// }
// length.value.convert()
// }
override val isFile: Boolean
get() = useStat { it.st_mode.convert<UInt>() flag S_IFREG } ?: false
override val isDirectory: Boolean
get() = useStat { it.st_mode.convert<UInt>() flag S_IFDIR } ?: false
override fun exists(): Boolean = getFileAttributes() != INVALID_FILE_ATTRIBUTES
private fun getFileAttributes(): DWORD = memScoped { GetFileAttributesW(absolutePath) }
override fun resolve(path: String): MiraiFile {
when (path) {
"." -> return this
".." -> return parent ?: this // root
}
if (ROOT_REGEX.find(path) != null) { // absolute
return MiraiFileImpl(path)
}
return MiraiFileImpl(this.absolutePath + SEPARATOR + path) // assuming path is 'appendable'
}
override fun resolve(file: MiraiFile): MiraiFile {
val parent = file.parent ?: return resolve(file.name)
return resolve(parent).resolve(file.name)
}
override fun createNewFile(): Boolean {
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
val handle = CreateFileW(
absolutePath,
GENERIC_READ,
FILE_SHARE_DELETE,
null,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
null
)
if (handle == null || handle == INVALID_HANDLE_VALUE) {
return false
}
if (CloseHandle(handle) == FALSE) {
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
}
return true
}
override fun delete(): Boolean {
return if (isFile) {
DeleteFileW(absolutePath) != 0
} else {
RemoveDirectoryW(absolutePath) != 0
}
}
override fun mkdir(): Boolean {
memScoped {
val v = alloc<_SECURITY_ATTRIBUTES>()
return CreateDirectoryW(absolutePath, v.ptr) != 0
}
}
override fun mkdirs(): Boolean {
this.parent?.mkdirs()
return mkdir()
}
override fun input(): Input {
// println(absolutePath)
// val handle2 = fopen(absolutePath, "rb") ?:throw IOException(
// "Failed to open file '$absolutePath'",
// PosixException.forErrno(posixFunctionName = "fopen()")
// )
// return PosixInputForFile(handle2)
// Will get I/O operation failed due to posix error code 2
val handle = CreateFileW(
absolutePath,
GENERIC_READ,
FILE_SHARE_DELETE,
null,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
null
)
if (handle == null || handle == INVALID_HANDLE_VALUE) throw IOException(
"Failed to open file '$absolutePath'",
PosixException.forErrno(posixFunctionName = "CreateFileW()")
)
return WindowsFileInput(handle)
}
override fun output(): Output {
// val handle2 = fopen(absolutePath, "wb")
// ?: throw IOException(
// "Failed to open file '$absolutePath'",
// PosixException.forErrno(posixFunctionName = "fopen()")
// )
// return PosixFileInstanceOutput(handle)
//
val handle = CreateFileW(
absolutePath,
GENERIC_WRITE,
FILE_SHARE_DELETE,
null,
(if (exists()) TRUNCATE_EXISTING else CREATE_NEW).toUInt(),
FILE_ATTRIBUTE_NORMAL,
null
)
if (handle == null || handle == INVALID_HANDLE_VALUE) throw IOException(
"Failed to open file '$absolutePath'",
PosixException.forErrno(posixFunctionName = "CreateFileW()")
)
return WindowsFileOutput(handle)
}
override fun hashCode(): Int {
return this.path.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (!isSameType(this, other)) return false
return this.path == other.path
}
override fun toString(): String {
return "MiraiFileImpl($path)"
}
}
internal class WindowsFileInput(private val file: HANDLE) : Input() {
private var closed = false
override fun fill(destination: Memory, offset: Int, length: Int): Int {
if (file == INVALID_HANDLE_VALUE) return 0
memScoped {
val n = alloc<DWORDVar>()
if (ReadFile(file, destination.pointer + offset, length.convert(), n.ptr, null) == FALSE) {
throw PosixException.forErrno(posixFunctionName = "ReadFile()").wrapIO()
}
return n.value.convert<UInt>().toInt()
}
}
override fun closeSource() {
if (closed) return
closed = true
if (file != INVALID_HANDLE_VALUE) {
if (CloseHandle(file) == FALSE) {
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
}
}
}
}
@Suppress("DEPRECATION")
internal class WindowsFileOutput(private val file: HANDLE) : Output() {
private var closed = false
override fun flush(source: Memory, offset: Int, length: Int) {
val end = offset + length
var currentOffset = offset
memScoped {
val written = alloc<UIntVar>()
while (currentOffset < end) {
val result = WriteFile(
file,
source.pointer + currentOffset.convert(),
(end - currentOffset).convert(),
written.ptr,
null
).convert<Int>()
if (result == FALSE) {
throw PosixException.forErrno(posixFunctionName = "WriteFile()").wrapIO()
}
currentOffset += written.value.toInt()
}
}
}
override fun closeDestination() {
if (closed) return
closed = true
if (CloseHandle(file) == FALSE) {
throw PosixException.forErrno(posixFunctionName = "CloseHandle()").wrapIO()
}
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import platform.windows.GetCurrentProcessorNumber
public actual fun availableProcessors(): Int =
GetCurrentProcessorNumber().toInt().coerceAtLeast(4) // somehow it worked on my machine but not on CI

View File

@ -1,46 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.math.absoluteValue
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
internal class WindowsMiraiFileImplTest : AbstractNativeMiraiFileImplTest() {
private val rand = Random.nextInt().absoluteValue
override val baseTempDir: MiraiFile = MiraiFile.create("C:\\mirai_unit_tests")
override val tempPath = "C:\\mirai_unit_tests\\temp$rand"
@Test
override fun parent() {
assertEquals("C:\\mirai_unit_tests", tempDir.parent!!.absolutePath)
super.parent()
}
@Test
override fun `canonical paths for non-canonical input`() {
super.`canonical paths for non-canonical input`()
// extra /sss/..
MiraiFile.create("$tempPath/sss/..").resolve("test").let {
assertPathEquals("${tempPath}/test", it.path) // Windows resolves always
assertPathEquals("${tempPath}/test", it.absolutePath)
}
}
@Test
override fun `resolve absolute`() {
MiraiFile.create("$tempPath/").resolve("C:\\mirai_unit_tests").let {
assertEquals("C:\\mirai_unit_tests", it.path)
assertEquals("C:\\mirai_unit_tests", it.absolutePath)
}
}
}

View File

@ -1,116 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
public actual fun String.decodeBase64(): ByteArray {
return Base64Impl.decode(this)
}
public actual fun ByteArray.encodeBase64(): String {
return Base64Impl.encode(this)
}
/**
* From <https://gist.github.com/EmilHernvall/953733>.
* @author EmilHernvall
*/
private object Base64Impl {
fun encode(data: ByteArray): String {
val tbl = charArrayOf(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
)
val buffer = StringBuilder()
var pad = 0
var i = 0
while (i < data.size) {
var b = data[i].toInt() and 0xFF shl 16 and 0xFFFFFF
if (i + 1 < data.size) {
b = b or (data[i + 1].toInt() and 0xFF shl 8)
} else {
pad++
}
if (i + 2 < data.size) {
b = b or (data[i + 2].toInt() and 0xFF)
} else {
pad++
}
for (j in 0 until 4 - pad) {
val c = b and 0xFC0000 shr 18
buffer.append(tbl[c])
b = b shl 6
}
i += 3
}
for (j in 0 until pad) {
buffer.append("=")
}
return buffer.toString()
}
fun decode(data: String): ByteArray {
val tbl = intArrayOf(
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
)
val bytes: ByteArray = data.toByteArray()
return buildPacket {
var i = 0
while (i < bytes.size) {
var b: Int
b = if (tbl[bytes[i].toInt()] != -1) {
tbl[bytes[i].toInt()] and 0xFF shl 18
} else {
i++
continue
}
var num = 0
if (i + 1 < bytes.size && tbl[bytes[i + 1].toInt()] != -1) {
b = b or (tbl[bytes[i + 1].toInt()] and 0xFF shl 12)
num++
}
if (i + 2 < bytes.size && tbl[bytes[i + 2].toInt()] != -1) {
b = b or (tbl[bytes[i + 2].toInt()] and 0xFF shl 6)
num++
}
if (i + 3 < bytes.size && tbl[bytes[i + 3].toInt()] != -1) {
b = b or (tbl[bytes[i + 3].toInt()] and 0xFF)
num++
}
while (num > 0) {
val c = b and 0xFF0000 shr 16
writeByte(c.toByte())
b = b shl 8
num--
}
i += 4
}
}.readBytes()
}
}

View File

@ -1,341 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import io.ktor.utils.io.bits.*
import io.ktor.utils.io.core.*
import kotlinx.cinterop.*
import platform.zlib.*
public actual val DEFAULT_BUFFER_SIZE: Int get() = 8192
public actual fun ByteArray.md5(offset: Int, length: Int): ByteArray {
MD5.create().run {
update(this@md5, offset, length)
return digest().bytes
}
}
public actual fun ByteArray.sha1(offset: Int, length: Int): ByteArray = SHA1.create().run {
update(this@sha1, offset, length)
return digest().bytes
}
public actual fun ByteArray.sha256(offset: Int, length: Int): ByteArray = SHA256.create().run {
update(this@sha256, offset, length)
return digest().bytes
}
/**
* WARNING: DO NOT SET THIS BUFFER TOO SMALL, OR YOU WILL SEE COMPRESSION ERROR.
*/
@set:TestOnly
public var ZLIB_BUFFER_SIZE: Long = 8192
public actual fun ByteArray.gzip(offset: Int, length: Int): ByteArray {
return GzipCompressionInput(this.toReadPacket(offset, length)).use { it.readBytes() }
}
public actual fun ByteArray.ungzip(offset: Int, length: Int): ByteArray {
return GzipDecompressionInput(this.toReadPacket(offset, length)).use { it.readBytes() }
}
public actual fun ByteArray.deflate(offset: Int, length: Int): ByteArray {
return DeflateInput(this.toReadPacket(offset, length)).use { it.readBytes() }
}
public actual fun ByteArray.inflate(offset: Int, length: Int): ByteArray {
return InflateInput(this.toReadPacket(offset, length)).use { it.readBytes() }
}
@Suppress("FunctionName")
public fun GzipCompressionInput(source: Input): Input {
return ZlibInput(
source = source,
zlibInit = { deflateInit2(it, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 or 16, 8, Z_DEFAULT_STRATEGY) },
zlibProcess = { z, flush -> deflate(z, flush) },
zlibHasPending = null,
zlibFlushMode = { if (it) Z_FINISH else Z_NO_FLUSH },
zlibEnd = { deflateEnd(it) },
)
}
@Suppress("FunctionName")
public actual fun GzipDecompressionInput(source: Input): Input {
return ZlibInput(
source = source,
zlibInit = { inflateInit2(it, 15 or 16) },
zlibProcess = { z, flush -> inflate(z, flush) },
zlibHasPending = null,
zlibFlushMode = { if (it) Z_SYNC_FLUSH else Z_NO_FLUSH },
zlibEnd = { inflateEnd(it) },
)
}
@Suppress("FunctionName")
public actual fun InflateInput(source: Input): Input {
return ZlibInput(
source = source,
zlibInit = { inflateInit2(it, 15) },
zlibProcess = { z, flush -> inflate(z, flush) },
zlibHasPending = null,
zlibFlushMode = { if (it) Z_SYNC_FLUSH else Z_NO_FLUSH },
zlibEnd = { inflateEnd(it) },
)
}
@Suppress("FunctionName")
public actual fun DeflateInput(source: Input): Input {
return ZlibInput(
source = source,
zlibInit = { deflateInit(it, Z_DEFAULT_COMPRESSION) },
zlibProcess = { z, flush -> deflate(z, flush) },
zlibHasPending = { z ->
memScoped {
val pendingBytes = cValue<UIntVar>().ptr
val pendingBits = cValue<IntVar>().ptr
if (deflatePending(z, pendingBytes, pendingBits) != Z_OK) {
false
} else {
pendingBytes.pointed.value > 0u || pendingBits.pointed.value > 0
}
}
},
zlibFlushMode = { if (it) Z_FINISH else Z_NO_FLUSH },
zlibEnd = { deflateEnd(it) },
)
}
/**
* Input will be closed.
*/
public actual fun Input.gzipAllAvailable(): ByteArray {
return GzipCompressionInput(this).use { it.readBytes() }
}
/**
* Input will be closed.
*/
public actual fun Input.ungzipAllAvailable(): ByteArray {
return GzipDecompressionInput(this).use { it.readBytes() }
}
/**
* Input will be closed.
*/
public actual fun Input.inflateAllAvailable(): ByteArray {
return InflateInput(this).use { it.readBytes() }
}
/**
* Input will be closed.
*/
public actual fun Input.deflateAllAvailable(): ByteArray {
return DeflateInput(this).use { it.readBytes() }
}
/**
* [source] will be closed on [ZlibInput.close]
*/
internal class ZlibInput(
private val source: Input,
zlibInit: (z_streamp) -> Int,
private val zlibProcess: (z_streamp, flush: Int) -> Int,
private val zlibHasPending: ((z_streamp) -> Boolean)?, // null lambda means operation not defined
private val zlibFlushMode: (shouldFlushAll: Boolean) -> Int,
private val zlibEnd: (z_streamp) -> Int,
) : Input() {
private val z: z_stream = nativeHeap.alloc()
// Zlib manual: https://refspecs.linuxbase.org/LSB_3.0.0/LSB-Core-generic/LSB-Core-generic/zlib-inflate-1.html
init {
val r = zlibInit(z.ptr)
if (r != 0) {
nativeHeap.free(z)
error("Failed to init zlib: $r (${getZlibError(r)})")
}
}
private var bufferReadableSize = 0L
private val inputBuffer = nativeHeap.allocArray<ByteVar>(ZLIB_BUFFER_SIZE)
private var closed = false
override fun close() {
if (closed) return
closed = true
debug { "close" }
super.close()
debug { "freeing inputBuffer" }
nativeHeap.free(inputBuffer)
debug { "freed" }
}
override fun closeSource() {
debug { "closeSource" }
source.close()
debug { "zlibEnd" }
zlibEnd(z.ptr)
debug { "zlibEnd done" }
}
override fun fill(destination: Memory, offset: Int, length: Int): Int {
require(offset in 0..destination.size32) { "invalid offset: $offset" }
require(length in 0..destination.size32) { "invalid length: $length" }
require(offset + length in 0..destination.size32) { "invalid offset and length: $offset, $length" }
debug { "prepare: bufferReadableSize = $bufferReadableSize" }
debug { "prepare: previous value: z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" }
val filled = try {
if (z.avail_in == 0u) {
// These two cases are similar.
// if (z.avail_out == 0u) {
// // Last time we used all the output, there is either something cached in Zlib, or no further source.
// } else {
// // We did not use all the inputs, meaning least time we used all avail_in.
// }
// bot input and output are used
val flush = updateAvailIn() ?: return 0
copyOutputsFromZlib(destination, offset, length, flush)
} else {
// Inputs not used up.
copyOutputsFromZlib(destination, offset, length, Z_NO_FLUSH)
}
} catch (e: Throwable) {
// If you throw this error up, ktor will somehow kill the process. (Ktor 2.0.2)
debug { e.printStackTrace(); "" }
return 0
}
check(filled in 0..length) { "Filled more than $length bytes: $filled" }
check(filled in 0..destination.size) { "Filled more than ${destination.size} bytes: $filled" }
return filled
}
private fun copyOutputsFromZlib(memory: Memory, offset: Int, length: Int, flush: Int): Int {
debug { "copyOutputsFromZlib, memory.offset = $offset, memory.length=$length, memory.size=${memory.size}" }
z.avail_out = length.convert()
z.next_out = (memory.pointer + offset)!!.reinterpret()
// We still have input, no need to update.
debug { "Set z.avail_out=${z.avail_out}, z.next_out=(memory.pointer + offset)!!.reinterpret()" }
debug { "Calling zlib, flush = $flush" }
val p = zlibProcess(z.ptr, flush)
when (p) {
Z_BUF_ERROR -> error("Zlib failed to process data. (Z_BUF_ERROR)")
Z_MEM_ERROR -> throw OutOfMemoryError("Insufficient native heap memory for Zlib. (Z_MEM_ERROR)")
Z_STREAM_ERROR -> error("Zlib failed to process data. (Z_STREAM_ERROR)")
Z_DATA_ERROR -> error("Zlib failed to process data. (Z_DATA_ERROR)")
Z_NEED_DICT -> error("Zlib failed to process data. (Z_NEED_DICT)")
else -> debug { "zlib: $p" }
}
val readSize = (length.toUInt() - z.avail_out).toInt()
debug { "Zlib produced readSize=$readSize bytes" }
// debug { "Partial output: ${memory.readBytes(bufferReadableSize).toUHexString()}" }
debug { "Now z.avail_in=${z.avail_in}, z.avail_out=${z.avail_out}" }
if (p == Z_FINISH) {
debug { "Zlib returned Z_FINISH. Ignoring result check." }
return readSize
}
if (p == Z_STREAM_END) {
debug { "Zlib returned Z_STREAM_END. Ignoring result check." }
return readSize
}
if (bufferReadableSize == 0L && (z.avail_in == 0u && source.endOfInput)) {
if (zlibHasPending?.invoke(z.ptr) == true) {
// has pending. So the data must be incomplete.
error("Failed to process data, possibly bad data inputted.")
// if (z.avail_in == 0u && source.endOfInput) {
// // no any input.
// } else {
// // there's some input, so we can still read.
// }
} else {
// no pending, but we should expect Z_FINISH in this case.
error("Zlib read 0 byte, but it should not happen.")
}
// can't read
}
return readSize
}
private fun updateAvailIn(): Int? {
val read = source.readAvailable(inputBuffer, 0, ZLIB_BUFFER_SIZE)
if (read == 0L) {
debug { "updateAvailIn: endOfInput, closing" }
close() // automatically close
return null // no more source available
}
bufferReadableSize = read
z.avail_in = read.toUInt()
val flush = zlibFlushMode(read < ZLIB_BUFFER_SIZE || source.endOfInput)
debug { "inputBuffer content: " + inputBuffer.readBytes(read.toInt()).toUHexString() }
z.next_in = inputBuffer.reinterpret()
debug { "Updated availIn: z.avail_in=${z.avail_in}, z.next_in = inputBuffer.reinterpret()" }
return flush
}
private companion object {
private fun getZlibError(it: Int): String {
return when (it) {
Z_DATA_ERROR -> "Z_DATA_ERROR"
Z_STREAM_ERROR -> "Z_STREAM_ERROR"
else -> "Unknown error $it"
}
}
private const val debugging = false
private inline fun debug(string: () -> String) {
if (debugging) println(string())
}
private inline fun debug() {
if (debugging) println()
}
}
}
//private fun ByteArray.callImpl(
// fn: (CValuesRef<uint8_tVar>, UInt, CValuesRef<SizedByteArray>) -> Boolean,
// offset: Int,
// length: Int
//): ByteArray {
// checkOffsetAndLength(offset, length)
//
// memScoped {
// val r = alloc<SizedByteArray>()
// if (!fn(toCValues().ptr.reinterpret<uint8_tVar>().plus(offset)!!, length.toUInt(), r.ptr)) {
// throw IllegalStateException("Failed platform implementation call")
// }
// try {
// return r.arr?.readBytes(r.size.toInt())!!
// } finally {
// free(r.arr)
// }
// }
//}

View File

@ -1,18 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
public actual inline fun measureTimeMillis(block: () -> Unit): Long {
val start = currentTimeMillis() // getTimeMillis in stdlib doesn't work on some native targets.
block()
return currentTimeMillis() - start
}

View File

@ -1,21 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.use as ktorUse
public actual typealias Closeable = io.ktor.utils.io.core.Closeable
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.LowPriorityInOverloadResolution
public actual inline fun <C : Closeable, R> C.use(block: (C) -> R): R = ktorUse(block)
public actual fun Closeable.asKtorCloseable(): io.ktor.utils.io.core.Closeable = this
public actual fun io.ktor.utils.io.core.Closeable.asMiraiCloseable(): Closeable = this

View File

@ -1,260 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import kotlinx.atomicfu.locks.ReentrantLock
import kotlinx.atomicfu.locks.reentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlin.reflect.KClass
@Suppress("FunctionName")
public actual fun <K : Any, V> ConcurrentHashMap(): MutableMap<K, V> {
return LockedConcurrentHashMap(reentrantLock())
}
private class LockedConcurrentHashSet<E>(
private val lock: ReentrantLock,
private val delegate: MutableSet<E> = mutableSetOf()
) : MutableSet<E> {
override fun add(element: E): Boolean = lock.withLock {
return delegate.add(element)
}
override fun addAll(elements: Collection<E>): Boolean = lock.withLock {
return delegate.addAll(elements)
}
override val size: Int get() = lock.withLock { delegate.size }
override fun clear() = lock.withLock { delegate.clear() }
override fun isEmpty(): Boolean = lock.withLock { delegate.isEmpty() }
override fun containsAll(elements: Collection<E>): Boolean = lock.withLock { delegate.containsAll(elements) }
override fun contains(element: E): Boolean = lock.withLock { delegate.contains(element) }
override fun iterator(): MutableIterator<E> = delegate.iterator() // no effect for locking
@Suppress("ConvertArgumentToSet")
override fun retainAll(elements: Collection<E>): Boolean = lock.withLock { delegate.retainAll(elements) }
@Suppress("ConvertArgumentToSet")
override fun removeAll(elements: Collection<E>): Boolean = lock.withLock { delegate.removeAll(elements) }
override fun remove(element: E): Boolean = lock.withLock { delegate.remove(element) }
override fun equals(other: Any?): Boolean = delegate == other
override fun hashCode(): Int = delegate.hashCode()
override fun toString(): String = delegate.toString()
}
private class LockedConcurrentCollection<E>(
private val lock: ReentrantLock,
private val delegate: MutableCollection<E>
) : MutableCollection<E> {
override fun add(element: E): Boolean = lock.withLock {
return delegate.add(element)
}
override fun addAll(elements: Collection<E>): Boolean = lock.withLock {
return delegate.addAll(elements)
}
override val size: Int get() = lock.withLock { delegate.size }
override fun clear() = lock.withLock { delegate.clear() }
override fun isEmpty(): Boolean = lock.withLock { delegate.isEmpty() }
override fun containsAll(elements: Collection<E>): Boolean = lock.withLock { delegate.containsAll(elements) }
override fun contains(element: E): Boolean = lock.withLock { delegate.contains(element) }
override fun iterator(): MutableIterator<E> = delegate.iterator() // no effect for locking
@Suppress("ConvertArgumentToSet")
override fun retainAll(elements: Collection<E>): Boolean = lock.withLock { delegate.retainAll(elements) }
@Suppress("ConvertArgumentToSet")
override fun removeAll(elements: Collection<E>): Boolean = lock.withLock { delegate.removeAll(elements) }
override fun remove(element: E): Boolean = lock.withLock { delegate.remove(element) }
override fun equals(other: Any?): Boolean = delegate == other
override fun hashCode(): Int = delegate.hashCode()
override fun toString(): String = delegate.toString()
}
private class LockedConcurrentHashMap<K : Any, V>(
private val lock: ReentrantLock,
private val delegate: MutableMap<K, V> = mutableMapOf()
) : MutableMap<K, V> {
override val entries: MutableSet<MutableMap.MutableEntry<K, V>>
get() = lock.withLock { LockedConcurrentHashSet(lock, delegate.entries) }
override val keys: MutableSet<K> get() = lock.withLock { LockedConcurrentHashSet(lock, delegate.keys) }
override val size: Int get() = lock.withLock { delegate.size }
override val values: MutableCollection<V>
get() = lock.withLock { LockedConcurrentCollection(lock, delegate.values) }
override fun clear() = lock.withLock { delegate.clear() }
override fun isEmpty(): Boolean = lock.withLock { delegate.isEmpty() }
override fun remove(key: K): V? = lock.withLock { delegate.remove(key) }
override fun putAll(from: Map<out K, V>) = lock.withLock { delegate.putAll(from) }
override fun put(key: K, value: V): V? = lock.withLock { delegate.put(key, value) }
override fun get(key: K): V? = lock.withLock { delegate.get(key) }
override fun containsValue(value: V): Boolean = lock.withLock { delegate.containsValue(value) }
override fun containsKey(key: K): Boolean = lock.withLock { delegate.containsKey(key) }
override fun equals(other: Any?): Boolean = delegate == other
override fun hashCode(): Int = delegate.hashCode()
override fun toString(): String = delegate.toString()
}
@Suppress("FunctionName")
public actual fun <E> ConcurrentLinkedDeque(): MutableDeque<E> {
return LockedConcurrentArrayDeque(reentrantLock())
}
private class LockedConcurrentArrayDeque<E>(
private val lock: ReentrantLock,
private val delegate: ArrayDeque<E> = ArrayDeque()
) : MutableDeque<E> {
override fun addFirst(element: E) = lock.withLock { delegate.addFirst(element) }
override fun add(element: E): Boolean {
lock.withLock { delegate.add(element) }
return true
}
override fun poll(): E? = lock.withLock { delegate.removeFirstOrNull() }
override fun offer(element: E): Boolean {
lock.withLock { delegate.addLast(element) }
return true
}
override val size: Int
get() = lock.withLock { delegate.size }
override fun clear() = lock.withLock { delegate.clear() }
override fun addAll(elements: Collection<E>): Boolean = lock.withLock { delegate.addAll(elements) }
override fun isEmpty(): Boolean = lock.withLock { delegate.isEmpty() }
override fun iterator(): MutableIterator<E> = delegate.iterator()
override fun retainAll(elements: Collection<E>): Boolean = lock.withLock { delegate.retainAll(elements) }
override fun removeAll(elements: Collection<E>): Boolean = lock.withLock { delegate.removeAll(elements) }
override fun remove(element: E): Boolean = lock.withLock { delegate.remove(element) }
override fun containsAll(elements: Collection<E>): Boolean = lock.withLock { delegate.containsAll(elements) }
override fun contains(element: E): Boolean = lock.withLock { delegate.contains(element) }
override fun equals(other: Any?): Boolean = delegate == other
override fun hashCode(): Int = delegate.hashCode()
override fun toString(): String = delegate.toString()
}
internal class ArrayDequeAsMutableDeque<E>(
private val delegate: ArrayDeque<E> = ArrayDeque()
) : MutableDeque<E> {
override fun addFirst(element: E) = delegate.addFirst(element)
override fun add(element: E): Boolean {
delegate.add(element)
return true
}
override fun poll(): E? = delegate.removeFirstOrNull()
override fun offer(element: E): Boolean {
delegate.addLast(element)
return true
}
override val size: Int
get() = delegate.size
override fun clear() = delegate.clear()
override fun addAll(elements: Collection<E>): Boolean = delegate.addAll(elements)
override fun isEmpty(): Boolean = delegate.isEmpty()
override fun iterator(): MutableIterator<E> = delegate.iterator()
override fun retainAll(elements: Collection<E>): Boolean = delegate.retainAll(elements)
override fun removeAll(elements: Collection<E>): Boolean = delegate.removeAll(elements)
override fun remove(element: E): Boolean = delegate.remove(element)
override fun containsAll(elements: Collection<E>): Boolean = delegate.containsAll(elements)
override fun contains(element: E): Boolean = delegate.contains(element)
override fun equals(other: Any?): Boolean = delegate == other
override fun hashCode(): Int = delegate.hashCode()
override fun toString(): String = delegate.toString()
}
@Suppress("FunctionName")
public actual fun <K : Enum<K>, V> EnumMap(clazz: KClass<K>): MutableMap<K, V> = mutableMapOf()
@Suppress("FunctionName")
public actual fun <E> ConcurrentSet(): MutableSet<E> {
return LockedConcurrentHashSet(reentrantLock())
}
public actual class LinkedList<E>(
private val delegate: ArrayDeque<E>
) : MutableList<E> by delegate {
public actual constructor() : this(ArrayDeque())
public actual fun addLast(element: E) {
return delegate.addLast(element)
}
}
public actual interface MutableDeque<E> : MutableQueue<E> {
public actual fun addFirst(element: E)
}
public actual interface MutableQueue<E> : MutableCollection<E> {
/**
* Adds the specified element to the collection.
*
* @return `true` if the element has been added, `false` if the collection does not support duplicates
* and the element is already contained in the collection.
* @throws IllegalStateException if the queue is full.
*/
public actual override fun add(element: E): Boolean
/**
* Removes and returns the head of the queue, `null` otherwise.
*/
public actual fun poll(): E?
/**
* Adds an element into the queue.
* @return `true` if the element has been added, `false` if queue is full.
*/
public actual fun offer(element: E): Boolean
}
/**
* Returns a [List] that cannot be cast to [MutableList] to modify it.
*/
public actual fun <T> List<T>.asImmutable(): List<T> = ImmutableList(this)
public actual fun <T> Collection<T>.asImmutable(): Collection<T> = ImmutableCollection(this)
public actual fun <T> Set<T>.asImmutable(): Set<T> = ImmutableSet(this)
internal class ImmutableList<T>(
private val delegate: List<T>
) : List<T> by delegate
internal class ImmutableCollection<T>(
private val delegate: Collection<T>
) : Collection<T> by delegate
internal class ImmutableSet<T>(
private val delegate: Set<T>
) : Set<T> by delegate

View File

@ -1,350 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.math.abs
import kotlin.math.min
import kotlin.math.sin
/*
* Note: All the declarations in this file are copied from 'com.soywiz.korlibs.krypto'. <https://github.com/korlibs/krypto>
*
* The license is attached:
MIT License
Copyright (c) 2017 Carlos Ballesteros Velasco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
----------------------------------
/**
* Based on CryptoJS v3.1.2
* code.google.com/p/crypto-js
* (c) 2009-2013 by Jeff Mott. All rights reserved.
* https://github.com/brix/crypto-js/blob/develop/LICENSE
*/
*/
internal inline fun Int.ext8(offset: Int) = (this ushr offset) and 0xFF
internal fun Int.rotateRight(amount: Int): Int = (this ushr amount) or (this shl (32 - amount))
internal fun Int.rotateLeft(bits: Int): Int = ((this shl bits) or (this ushr (32 - bits)))
internal fun arraycopy(src: ByteArray, srcPos: Int, dst: ByteArray, dstPos: Int, count: Int) =
src.copyInto(dst, dstPos, srcPos, srcPos + count)
internal fun arraycopy(src: IntArray, srcPos: Int, dst: IntArray, dstPos: Int, count: Int) =
src.copyInto(dst, dstPos, srcPos, srcPos + count)
internal fun ByteArray.readU8(o: Int): Int = this[o].toInt() and 0xFF
internal fun ByteArray.readS32_be(o: Int): Int =
(readU8(o + 3) shl 0) or (readU8(o + 2) shl 8) or (readU8(o + 1) shl 16) or (readU8(o + 0) shl 24)
internal abstract class Hasher(val chunkSize: Int, val digestSize: Int) {
private val chunk = ByteArray(chunkSize)
private var writtenInChunk = 0
private var totalWritten = 0L
fun reset(): Hasher {
coreReset()
writtenInChunk = 0
totalWritten = 0L
return this
}
fun update(data: ByteArray, offset: Int, count: Int): Hasher {
var curr = offset
var left = count
while (left > 0) {
val remainingInChunk = chunkSize - writtenInChunk
val toRead = min(remainingInChunk, left)
arraycopy(data, curr, chunk, writtenInChunk, toRead)
left -= toRead
curr += toRead
writtenInChunk += toRead
if (writtenInChunk >= chunkSize) {
writtenInChunk -= chunkSize
coreUpdate(chunk)
}
}
totalWritten += count
return this
}
fun digestOut(out: ByteArray) {
val pad = corePadding(totalWritten)
var padPos = 0
while (padPos < pad.size) {
val padSize = chunkSize - writtenInChunk
arraycopy(pad, padPos, chunk, writtenInChunk, padSize)
coreUpdate(chunk)
writtenInChunk = 0
padPos += padSize
}
coreDigest(out)
coreReset()
}
protected abstract fun coreReset()
protected abstract fun corePadding(totalWritten: Long): ByteArray
protected abstract fun coreUpdate(chunk: ByteArray)
protected abstract fun coreDigest(out: ByteArray)
fun update(data: ByteArray) = update(data, 0, data.size)
fun digest(): Hash = Hash(ByteArray(digestSize).also { digestOut(it) })
}
internal value class Hash(val bytes: ByteArray)
internal open class HasherFactory(val create: () -> Hasher) {
fun digest(data: ByteArray) = create().also { it.update(data, 0, data.size) }.digest()
inline fun digest(temp: ByteArray = ByteArray(0x1000), readBytes: (data: ByteArray) -> Int): Hash =
this.create().also {
while (true) {
val count = readBytes(temp)
if (count <= 0) break
it.update(temp, 0, count)
}
}.digest()
}
internal class MD5 : Hasher(chunkSize = 64, digestSize = 16) {
companion object : HasherFactory({ MD5() }) {
private val S = intArrayOf(7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21)
private val T = IntArray(64) { ((1L shl 32) * abs(sin(1.0 + it))).toLong().toInt() }
}
private val r = IntArray(4)
private val o = IntArray(4)
private val b = IntArray(16)
init {
coreReset()
}
override fun coreReset() {
r[0] = 0x67452301
r[1] = 0xEFCDAB89.toInt()
r[2] = 0x98BADCFE.toInt()
r[3] = 0x10325476
}
override fun coreUpdate(chunk: ByteArray) {
for (j in 0 until 64) b[j ushr 2] = (chunk[j].toInt() shl 24) or (b[j ushr 2] ushr 8)
for (j in 0 until 4) o[j] = r[j]
for (j in 0 until 64) {
val d16 = j / 16
val f = when (d16) {
0 -> (r[1] and r[2]) or (r[1].inv() and r[3])
1 -> (r[1] and r[3]) or (r[2] and r[3].inv())
2 -> r[1] xor r[2] xor r[3]
3 -> r[2] xor (r[1] or r[3].inv())
else -> 0
}
val bi = when (d16) {
0 -> j
1 -> (j * 5 + 1) and 0x0F
2 -> (j * 3 + 5) and 0x0F
3 -> (j * 7) and 0x0F
else -> 0
}
val temp = r[1] + (r[0] + f + b[bi] + T[j]).rotateLeft(S[(d16 shl 2) or (j and 3)])
r[0] = r[3]
r[3] = r[2]
r[2] = r[1]
r[1] = temp
}
for (j in 0 until 4) r[j] += o[j]
}
override fun corePadding(totalWritten: Long): ByteArray {
val numberOfBlocks = ((totalWritten + 8) / chunkSize) + 1
val totalWrittenBits = totalWritten * 8
return ByteArray(((numberOfBlocks * chunkSize) - totalWritten).toInt()).apply {
this[0] = 0x80.toByte()
for (i in 0 until 8) this[this.size - 8 + i] = (totalWrittenBits ushr (8 * i)).toByte()
}
}
override fun coreDigest(out: ByteArray) {
for (it in 0 until 16) out[it] = (r[it / 4] ushr ((it % 4) * 8)).toByte()
}
}
internal abstract class SHA(chunkSize: Int, digestSize: Int) : Hasher(chunkSize, digestSize) {
override fun corePadding(totalWritten: Long): ByteArray {
val tail = totalWritten % 64
val padding = (if (64 - tail >= 9) 64 - tail else 128 - tail)
val pad = ByteArray(padding.toInt()).apply { this[0] = 0x80.toByte() }
val bits = (totalWritten * 8)
for (i in 0 until 8) pad[pad.size - 1 - i] = ((bits ushr (8 * i)) and 0xFF).toByte()
return pad
}
}
internal class SHA1 : SHA(chunkSize = 64, digestSize = 20) {
companion object : HasherFactory({ SHA1() }) {
private val H = intArrayOf(
0x67452301L.toInt(),
0xEFCDAB89L.toInt(),
0x98BADCFEL.toInt(),
0x10325476L.toInt(),
0xC3D2E1F0L.toInt()
)
private const val K0020: Int = 0x5A827999L.toInt()
private const val K2040: Int = 0x6ED9EBA1L.toInt()
private const val K4060: Int = 0x8F1BBCDCL.toInt()
private const val K6080: Int = 0xCA62C1D6L.toInt()
}
private val w = IntArray(80)
private val h = IntArray(5)
override fun coreReset(): Unit {
arraycopy(H, 0, h, 0, 5)
}
init {
coreReset()
}
override fun coreUpdate(chunk: ByteArray) {
for (j in 0 until 16) w[j] = chunk.readS32_be(j * 4)
for (j in 16 until 80) w[j] = (w[j - 3] xor w[j - 8] xor w[j - 14] xor w[j - 16]).rotateLeft(1)
var a = h[0]
var b = h[1]
var c = h[2]
var d = h[3]
var e = h[4]
for (j in 0 until 80) {
val temp = a.rotateLeft(5) + e + w[j] + when (j / 20) {
0 -> ((b and c) or ((b.inv()) and d)) + K0020
1 -> (b xor c xor d) + K2040
2 -> ((b and c) xor (b and d) xor (c and d)) + K4060
else -> (b xor c xor d) + K6080
}
e = d
d = c
c = b.rotateLeft(30)
b = a
a = temp
}
h[0] += a
h[1] += b
h[2] += c
h[3] += d
h[4] += e
}
override fun coreDigest(out: ByteArray) {
for (n in out.indices) out[n] = (h[n / 4] ushr (24 - 8 * (n % 4))).toByte()
}
}
internal class SHA256 : SHA(chunkSize = 64, digestSize = 32) {
companion object : HasherFactory({ SHA256() }) {
private val H = intArrayOf(
0x6a09e667, -0x4498517b, 0x3c6ef372, -0x5ab00ac6,
0x510e527f, -0x64fa9774, 0x1f83d9ab, 0x5be0cd19
)
private val K = intArrayOf(
0x428a2f98, 0x71374491, -0x4a3f0431, -0x164a245b,
0x3956c25b, 0x59f111f1, -0x6dc07d5c, -0x54e3a12b,
-0x27f85568, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, -0x7f214e02, -0x6423f959, -0x3e640e8c,
-0x1b64963f, -0x1041b87a, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
-0x67c1aeae, -0x57ce3993, -0x4ffcd838, -0x40a68039,
-0x391ff40d, -0x2a586eb9, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, -0x7e3d36d2, -0x6d8dd37b,
-0x5d40175f, -0x57e599b5, -0x3db47490, -0x3893ae5d,
-0x2e6d17e7, -0x2966f9dc, -0xbf1ca7b, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, -0x7b3787ec, -0x7338fdf8,
-0x6f410006, -0x5baf9315, -0x41065c09, -0x398e870e
)
}
private val h = IntArray(8)
private val r = IntArray(8)
private val w = IntArray(64)
init {
coreReset()
}
override fun coreReset() {
arraycopy(H, 0, h, 0, 8)
}
override fun coreUpdate(chunk: ByteArray) {
arraycopy(h, 0, r, 0, 8)
for (j in 0 until 16) w[j] = chunk.readS32_be(j * 4)
for (j in 16 until 64) {
val s0 = w[j - 15].rotateRight(7) xor w[j - 15].rotateRight(18) xor w[j - 15].ushr(3)
val s1 = w[j - 2].rotateRight(17) xor w[j - 2].rotateRight(19) xor w[j - 2].ushr(10)
w[j] = w[j - 16] + s0 + w[j - 7] + s1
}
for (j in 0 until 64) {
val s1 = r[4].rotateRight(6) xor r[4].rotateRight(11) xor r[4].rotateRight(25)
val ch = r[4] and r[5] xor (r[4].inv() and r[6])
val t1 = r[7] + s1 + ch + K[j] + w[j]
val s0 = r[0].rotateRight(2) xor r[0].rotateRight(13) xor r[0].rotateRight(22)
val maj = r[0] and r[1] xor (r[0] and r[2]) xor (r[1] and r[2])
val t2 = s0 + maj
r[7] = r[6]
r[6] = r[5]
r[5] = r[4]
r[4] = r[3] + t1
r[3] = r[2]
r[2] = r[1]
r[1] = r[0]
r[0] = t1 + t2
}
for (j in 0 until 8) h[j] += r[j]
}
override fun coreDigest(out: ByteArray) {
for (n in out.indices) out[n] = (h[n / 4] ushr (24 - 8 * (n % 4))).toByte()
}
}

View File

@ -1,72 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
public actual suspend inline fun <R> runBIO(noinline block: () -> R): R {
return block()
}
public actual suspend inline fun <T, R> T.runBIO(crossinline block: T.() -> R): R {
return block()
}
/**
* For code
* ```
* try {
* job(new)
* } catch (e: Throwable) {
* throw IllegalStateException("Exception in attached Job '$name'", e.unwrapCancellationException())
* }
* ```
*
* Original stacktrace, you mainly see `StateSwitchingException` which is useless to locate the code where real cause `ForceOfflineException` is thrown.
* ```
* Exception in thread "DefaultDispatcher-worker-1 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init'
* at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40)
* at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
* at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
* at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
* Caused by: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@4abf6d30)
* at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258)
* at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404)
* ```
*
* Real stacktrace (with [unwrapCancellationException]), you directly have `ForceOfflineException`, also you wont lose information of `StateSwitchingException`
* ```
* Exception in thread "DefaultDispatcher-worker-2 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init'
* at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40)
* at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
* at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
* at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
* at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
* Caused by: net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94
* at net.mamoe.mirai.utils.MiraiUtils__CoroutineUtilsKt.unwrapCancellationException(CoroutineUtils.kt:141)
* at net.mamoe.mirai.utils.MiraiUtils.unwrapCancellationException(Unknown Source)
* ... 7 more
* Suppressed: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94)
* at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258)
* at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404)
* ```
*/
@Suppress("unused")
public actual inline fun <reified E> Throwable.unwrap(addSuppressed: Boolean): Throwable {
if (this !is E) return this
return this.findCause { it !is E }
?.also { if (addSuppressed) it.addSuppressed(this) }
?: this
}

View File

@ -1,22 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
internal actual fun hash(e: Throwable): Long {
var hashCode = 1L
val trace = e.getStackTraceAddresses()
for (stackTraceAddress in trace) {
hashCode = (hashCode xor stackTraceAddress).shl(1)
}
// Somehow stacktrace analysis is on my own Windows machine but not on GitHub Actions.
// Hashing with a class to tentatively not filter out different types.
return hashCode xor e::class.hashCode().toLongUnsigned()
}

View File

@ -1,225 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import io.ktor.utils.io.bits.*
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import kotlinx.cinterop.*
import platform.posix.*
/**
* Multiplatform implementation of file operations.
*/
public actual interface MiraiFile {
/**
* Name of this file or directory. Can be '.' and '..' if created by
*/
public actual val name: String
/**
* Parent of this file or directory.
*/
public actual val parent: MiraiFile?
/**
* Input path from [create].
*/
public actual val path: String
/**
* Normalized absolute [path].
*/
public actual val absolutePath: String
public actual val length: Long
public actual val isFile: Boolean
public actual val isDirectory: Boolean
public actual fun exists(): Boolean
/**
* Resolves a [MiraiFile] representing the [path] based on this [MiraiFile]. Result path is not guaranteed to be normalized.
*/
public actual fun resolve(path: String): MiraiFile
public actual fun resolve(file: MiraiFile): MiraiFile
public actual fun createNewFile(): Boolean
public actual fun delete(): Boolean
public actual fun mkdir(): Boolean
public actual fun mkdirs(): Boolean
public actual fun input(): Input
public actual fun output(): Output
public actual companion object {
public actual fun create(path: String): MiraiFile = MiraiFileImpl(path)
public actual fun getWorkingDir(): MiraiFile = MiraiFileImpl.getWorkingDir()
}
}
private val deleteFile =
staticCFunction<CPointer<ByteVarOf<Byte>>?, CPointer<stat>?, Int, CPointer<FTW>?, Int> { pathPtr, _, _, _ ->
val path = pathPtr!!.toKString()
if (remove(path) < 0) {
-1
} else {
0
}
}
public actual fun MiraiFile.deleteRecursively(): Boolean {
return nftw(absolutePath, deleteFile, 10, FTW_DEPTH or FTW_MOUNT or FTW_PHYS) >= 0
}
internal expect class MiraiFileImpl(path: String) : MiraiFile {
companion object {
public fun getWorkingDir(): MiraiFile
}
}
/*
Data from https://man7.org/linux/man-pages/man2/lstat.2.html
st_dev This field describes the device on which this file
resides. (The major(3) and minor(3) macros may be useful
to decompose the device ID in this field.)
st_ino This field contains the file's inode number.
st_mode
This field contains the file type and mode. See inode(7)
for further information.
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
st_nlink
This field contains the number of hard links to the file.
st_uid This field contains the user ID of the owner of the file.
st_gid This field contains the ID of the group owner of the file.
st_rdev
This field describes the device that this file (inode)
represents.
st_size
This field gives the size of the file (if it is a regular
file or a symbolic link) in bytes. The size of a symbolic
link is the length of the pathname it contains, without a
terminating null byte.
st_blksize
This field gives the "preferred" block size for efficient
filesystem I/O.
st_blocks
This field indicates the number of blocks allocated to the
file, in 512-byte units. (This may be smaller than
st_size/512 when the file has holes.)
st_atime
This is the time of the last access of file data.
st_mtime
This is the time of last modification of file data.
st_ctime
This is the file's last status change timestamp (time of
last change to the inode).
*/
internal inline fun <R> MiraiFileImpl.useStat(block: (stat) -> R): R? {
memScoped {
val stat = alloc<stat>()
val ret = stat(absolutePath, stat.ptr)
if (ret != 0) return null
return block(stat)
}
}
internal class FileNotFoundException(message: String, cause: Throwable? = null) :
IOException(message, cause)
@Suppress("DEPRECATION")
internal class PosixFileInstanceOutput(val file: CPointer<FILE>) : Output() {
private var closed = false
override fun flush(source: Memory, offset: Int, length: Int) {
val end = offset + length
var currentOffset = offset
while (currentOffset < end) {
val result = fwrite(
source.pointer + currentOffset.convert(),
sizeOf<ByteVar>().convert(),
(end - currentOffset).convert(),
file.cast()
).convert<Int>()
if (result == 0) {
throw PosixException.forErrno(posixFunctionName = "fwrite()").wrapIO()
}
currentOffset += result
}
}
override fun closeDestination() {
if (closed) return
closed = true
if (fclose(file) != 0) {
throw PosixException.forErrno(posixFunctionName = "fclose").wrapIO()
}
}
}
@Suppress("DEPRECATION")
internal class PosixInputForFile(val file: CPointer<FILE>) : Input() {
private var closed = false
override fun fill(destination: Memory, offset: Int, length: Int): Int {
val size = fread(
destination.pointer + offset.convert(),
sizeOf<ByteVar>().convert(),
length.convert(),
file.cast()
).toInt()
if (size == 0) {
if (feof(file) != 0) return 0
throw PosixException.forErrno(posixFunctionName = "read()").wrapIO()
}
return size
}
override fun closeSource() {
if (closed) return
closed = true
if (fclose(file) != 0) {
throw PosixException.forErrno(posixFunctionName = "fclose()").wrapIO()
}
}
}
public fun PosixException.wrapIO(): IOException =
IOException("I/O operation failed due to posix error code $errno", this)

View File

@ -1,21 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.cinterop.*
public inline infix fun UShort.flag(flag: UShort): Boolean = this and flag != 0u.toUShort()
public inline infix fun UInt.flag(flag: UInt): Boolean = this and flag != 0u
public inline infix fun UInt.flag(flag: Int): Boolean = this and flag.toUInt() != 0u
public inline infix fun Int.flag(flag: UInt): Boolean = this.toUInt() and flag != 0u
public inline infix fun ULong.flag(flag: ULong): Boolean = this and flag != 0uL
public val NULL_PTR: COpaquePointerVar = nativeHeap.alloc()
public inline fun <reified T : NativePointed> nullPtr(): T = NULL_PTR.reinterpret()

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.serializer
internal actual object SecretsProtectionPlatform {
actual fun impl_asString(data: Any): String {
return (data as ByteArray).decodeToString()
}
actual fun impl_asByteArray(data: Any): ByteArray {
return data as ByteArray
}
actual fun impl_getSize(data: Any): Int {
return data.cast<ByteArray>().size
}
actual fun escape(data: ByteArray): Any {
return data
}
actual object EscapedStringSerializer : KSerializer<SecretsProtection.EscapedString> by String.serializer().map(
String.serializer().descriptor.copy("EscapedString"),
deserialize = { SecretsProtection.EscapedString(it.encodeToByteArray()) },
serialize = { it.data.cast<ByteArray>().decodeToString() }
)
actual object EscapedByteBufferSerializer :
KSerializer<SecretsProtection.EscapedByteBuffer> by ByteArraySerializer().map(
ByteArraySerializer().descriptor.copy("EscapedByteBuffer"),
deserialize = { SecretsProtection.EscapedByteBuffer(it) },
serialize = { it.data.cast() }
)
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import net.mamoe.mirai.utils.Services.qualifiedNameOrFail
import kotlin.reflect.KClass
@Suppress("UNCHECKED_CAST")
public actual fun <T : Any> loadServiceOrNull(
clazz: KClass<out T>,
fallbackImplementation: String?
): T? =
Services.firstImplementationOrNull(qualifiedNameOrFail(clazz)) as T?
public actual fun <T : Any> loadService(
clazz: KClass<out T>,
fallbackImplementation: String?
): T = loadServiceOrNull(clazz, fallbackImplementation)
?: error("Could not load service '${clazz.qualifiedName ?: clazz}'. Current services: ${Services.print()}")
public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> =
Services.implementations(qualifiedNameOrFail(clazz)).orEmpty().map { it.value }.castUp()

View File

@ -1,17 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
public actual fun localIpAddress(): String = "192.168.1.123"
internal actual fun isSameClassPlatform(object1: Any, object2: Any): Boolean {
return object1::class == object2::class
}

View File

@ -1,14 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
internal actual fun getPlatformDefaultStructureToStringTransformer(): StructureToStringTransformer? {
return null
}

View File

@ -1,58 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("RedundantVisibilityModifier")
package net.mamoe.mirai.utils
import kotlinx.atomicfu.locks.ReentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlinx.cinterop.*
import platform.posix.*
/**
* 时间戳
*/
@OptIn(UnsafeNumber::class)
public actual fun currentTimeMillis(): Long {
// Do not use getTimeMillis from stdlib, it doesn't support iosSimulatorArm64
memScoped {
val spec = alloc<timespec>()
clock_gettime(CLOCK_REALTIME.convert(), spec.ptr)
return (spec.tv_sec * 1000 + spec.tv_nsec.convert<Long>() / 1e6).toLong()
}
}
private val timeLock = ReentrantLock()
public actual fun formatTime(epochTimeMillis: Long, format: String?): String = timeLock.withLock {
val strftimeFormat = format
?.replace("yyyy", "%Y")
?.replace("MM", "%m")
?.replace("dd", "%d")
?.replace("HH", "%H")
?.replace("mm", "%M")
?.replace("ss", "%S")
?: "%Y-%m-%d %H:%M:%S"
memScoped {
val timeT = alloc<time_tVar>()
timeT.value = epochTimeMillis / 1000
// http://www.cplusplus.com/reference/clibrary/ctime/localtime/
// tm returns a static pointer which doesn't need to free
val tm = localtime(timeT.ptr) // localtime is not thread-safe
val bb = allocArray<ByteVar>(40)
strftime(bb, 40, strftimeFormat, tm);
bb.toKString()
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
private val properties: MutableMap<String, String> = ConcurrentHashMap()
internal actual fun getProperty(name: String, default: String): String? {
return properties.getOrElse(name) { default }
}
internal actual fun setProperty(name: String, value: String) {
properties[name] = value
}

View File

@ -1,8 +0,0 @@
/target
Cargo.lock
myrust.h
*.iml
src/bindings.rs
/*.h

View File

@ -1,19 +0,0 @@
[package]
name = "mirai_core_utils_i"
version = "0.1.0"
[dependencies]
md5 = "0.7.0"
sha1 = "0.10.1"
flate2 = "1.0.23"
libc = "0.2.126"
#chashmap = "2.2.2"
[lib]
name = "mirai_core_utils_i"
crate-type = ["cdylib"] # Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
[build-dependencies]
bindgen = "0.53.1"
cbindgen = "0.20.0"

View File

@ -1,32 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 使 GNU AFFERO GENERAL PUBLIC LICENSE version 3 , .
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
extern crate bindgen;
extern crate cbindgen;
use std::env;
use std::path::PathBuf;
use cbindgen::Config;
use cbindgen::Language::C;
fn main() {
// let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
// cbindgen::Builder::new()
// .with_crate(crate_dir)
// .with_language(C)
// .generate()
// .expect("Unable to generate bindings")
// .write_to_file("nativeInterop.h");
println!("cargo:rustc-link-search=../../build/bin/native/debugShared");
println!("cargo:rustc-link-lib=mirai_core_utils");
}

View File

@ -1,10 +0,0 @@
# This is a template cbindgen.toml file with all of the default values.
# Some values are commented out because their absence is the real default.
#
# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
# for detailed documentation of every option here.
language = "C"

View File

@ -1,27 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 使 GNU AFFERO GENERAL PUBLIC LICENSE version 3 , .
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
// use std::ops::DerefMut;
// use std::ptr::{null, null_mut};
//
// use chashmap::CHashMap;
// use libc::c_void;
// //
// #[no_mangle]
// pub extern "C" fn mirai_chmap_create() -> *mut c_void {
// let map = CHashMap::<*mut c_void, *mut c_void>::new();
// // Box::into_raw(Box::new(map))
// return Box::into_raw(Box::new(map)) as *mut c_void;
// }
//
// #[no_mangle]
// pub unsafe extern "C" fn mirai_chmap_put(map: *const c_void, key: *const c_void, value: *const c_void) -> *const c_void {
// let chmap = Box::from_raw(map as *mut CHashMap::<*const c_void, *const c_void>);
// return chmap.insert(key, value).unwrap_or(null());
// }

View File

@ -1,125 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 使 GNU AFFERO GENERAL PUBLIC LICENSE version 3 , .
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
use std::io::{BufReader, Read, Write};
use flate2::Compression;
use flate2::write::{DeflateDecoder, DeflateEncoder, GzDecoder, GzEncoder, ZlibDecoder, ZlibEncoder};
use libc::{malloc, read, size_t};
use sha1::{Digest, Sha1};
use sha1::digest::{Output, OutputSizeUser};
use sha1::digest::generic_array::GenericArray;
#[no_mangle]
#[repr(C)]
pub struct SizedByteArray {
arr: *mut u8,
size: u32,
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_md5(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let result = md5::compute(data);
let size = 16;
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_sha1(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let mut hasher = Sha1::new();
hasher.update(data);
let result = hasher.finalize();
let size = 16;
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_gzip(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
let result = encoder.write_all(data).and_then(|_| { encoder.finish() });
if result.is_err() { return false; }
let result = result.unwrap();
let size = result.len();
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_ungzip(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let mut encoder = GzDecoder::new(Vec::new());
let result = encoder.write_all(data).and_then(|_| { encoder.finish() });
if result.is_err() { return false; }
let result = result.unwrap();
let size = result.len();
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_deflate(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
let result = encoder.write_all(data).and_then(|_| { encoder.finish() });
if result.is_err() { return false; }
let result = result.unwrap();
let size = result.len();
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}
#[no_mangle]
pub unsafe extern "C" fn mirai_crypto_inflate(data: *const u8, len: u32, ret: &mut SizedByteArray) -> bool {
let data = unsafe { std::slice::from_raw_parts(data, len as usize) };
let mut encoder = ZlibDecoder::new(Vec::new());
let result = encoder.write_all(data).and_then(|_| { encoder.finish() });
if result.is_err() { return false; }
let result = result.unwrap();
let size = result.len();
let mut memory = malloc(size).cast();
memory.copy_from(result.as_ptr(), size);
ret.arr = memory;
ret.size = size as u32;
return true;
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 使 GNU AFFERO GENERAL PUBLIC LICENSE version 3 , .
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
// extern crate chashmap;
extern crate core;
extern crate flate2;
extern crate libc;
extern crate sha1;
/// cbindgen:ignore
mod bindings;
mod crypto;
mod chmap;

View File

@ -1,183 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.errors.*
import kotlin.test.*
internal abstract class AbstractNativeMiraiFileImplTest {
protected abstract val baseTempDir: MiraiFile // MiraiFile.create("/Users/Shared/mirai_test")
protected abstract val tempPath: String
protected val tempDir by lazy {
MiraiFile.create(tempPath).apply {
assertTrue("Failed to make temp directory: ${this.absolutePath}") { mkdirs() }
}
}
@AfterTest
fun afterTest() {
println("Cleaning up...")
println("deleteRecursively:" + baseTempDir.deleteRecursively())
}
@BeforeTest
fun init() {
println("Test start")
assertTrue { tempDir.exists() }
}
@Test
fun `canonical paths for canonical input`() {
assertPathEquals(tempPath, tempDir.path)
assertPathEquals(tempPath, tempDir.absolutePath)
}
@Test
protected open fun parent() {
assertEquals(tempDir, tempDir.resolve("s").parent)
assertEquals(tempDir.parent, tempDir.resolve(".."))
}
@Test
open fun `canonical paths for non-canonical input`() {
// extra /
MiraiFile.create("$tempPath/").resolve("test").let {
assertPathEquals("${tempPath}/test", it.path)
assertPathEquals("${tempPath}/test", it.absolutePath)
}
// extra //
MiraiFile.create("$tempPath//").resolve("test").let {
assertPathEquals("${tempPath}/test", it.path)
assertPathEquals("${tempPath}/test", it.absolutePath)
}
// extra /.
MiraiFile.create("$tempPath/.").resolve("test").let {
assertPathEquals("${tempPath}/test", it.path)
assertPathEquals("${tempPath}/test", it.absolutePath)
}
// extra /./.
MiraiFile.create("$tempPath/./.").resolve("test").let {
assertPathEquals("${tempPath}/test", it.path)
assertPathEquals("${tempPath}/test", it.absolutePath)
}
}
@Test
abstract fun `resolve absolute`()
@Test
fun `exits createNewFile mkdir length`() {
assertTrue { tempDir.exists() }
assertFalse { tempDir.resolve("not_existing_file.txt").exists() }
assertEquals(0L, tempDir.resolve("not_existing_file.txt").length)
assertTrue { tempDir.resolve("not_existing_file.txt").createNewFile() }
assertEquals(0L, tempDir.resolve("not_existing_file.txt").length)
assertTrue { tempDir.resolve("not_existing_file.txt").exists() }
assertFalse { tempDir.resolve("not_existing_dir").exists() }
assertEquals(0L, tempDir.resolve("not_existing_dir").length)
assertTrue { tempDir.resolve("not_existing_dir").mkdir() }
// assertNotEquals(0L, tempDir.resolve("not_existing_dir").length) // length is platform-dependent, on Windows it is 0 but on unix it is not
assertTrue { tempDir.resolve("not_existing_dir").exists() }
}
@Test
fun `isFile isDirectory`() {
assertTrue { tempDir.exists() }
println("1")
assertFalse { tempDir.resolve("not_existing_file.txt").exists() }
assertEquals(false, tempDir.resolve("not_existing_file.txt").isFile)
println("1")
assertEquals(false, tempDir.resolve("not_existing_file.txt").isDirectory)
println("1")
assertTrue { tempDir.resolve("not_existing_file.txt").createNewFile() }
assertEquals(true, tempDir.resolve("not_existing_file.txt").isFile)
assertEquals(false, tempDir.resolve("not_existing_file.txt").isDirectory)
println("1")
assertTrue { tempDir.resolve("not_existing_file.txt").exists() }
println("1")
assertFalse { tempDir.resolve("not_existing_dir").exists() }
assertEquals(false, tempDir.resolve("not_existing_dir").isFile)
assertEquals(false, tempDir.resolve("not_existing_dir").isDirectory)
println("1")
assertTrue { tempDir.resolve("not_existing_dir").mkdir() }
assertEquals(false, tempDir.resolve("not_existing_dir").isFile)
assertEquals(true, tempDir.resolve("not_existing_dir").isDirectory)
println("1")
assertTrue { tempDir.resolve("not_existing_dir").exists() }
}
@Test
fun writeText() {
// new file
tempDir.resolve("writeText1.txt").let { file ->
val text = "some text"
file.writeText(text)
assertEquals(text.length, file.length.toInt())
}
// override
tempDir.resolve("writeText1.txt").let { file ->
val text = "some other text"
file.writeText(text)
assertEquals(text.length, file.length.toInt())
}
}
@Test
fun readText() {
tempDir.resolve("readText2.txt").let { file ->
assertTrue { !file.exists() }
assertFailsWith<IOException> { file.readText() }
val text = "some text"
file.writeText(text)
assertEquals(text, file.readText())
}
}
private val bigText = "some text".repeat(10000)
@Test
fun writeBigText() {
// new file
tempDir.resolve("writeText3.txt").let { file ->
file.writeText(bigText)
assertEquals(bigText.length, file.length.toInt())
}
// override
tempDir.resolve("writeText4.txt").let { file ->
file.writeText(bigText)
assertEquals(bigText.length, file.length.toInt())
}
}
@Test
fun readBigText() {
tempDir.resolve("readText4.txt").let { file ->
assertTrue { !file.exists() }
assertFailsWith<IOException> { file.readText() }
file.writeText(bigText)
println("reading text")
val read = file.readText()
assertEquals(bigText.length, read.length)
assertEquals(bigText, read)
}
}
protected fun assertPathEquals(expected: String, actual: String, message: String? = null) {
asserter.assertEquals(message, expected.replace("\\", "/"), actual.replace("\\", "/"))
}
}

View File

@ -1,72 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:OptIn(TestOnly::class)
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
import kotlin.test.Test
import kotlin.test.assertEquals
internal actual class ByteArrayOpTest : CommonByteArrayOpTest() {
private fun <R> withZlibBufferSize(size: Long, block: () -> R): R {
val o = ZLIB_BUFFER_SIZE
ZLIB_BUFFER_SIZE = size
try {
return block()
} finally {
ZLIB_BUFFER_SIZE = o
}
}
@Test
fun testDeflateBig() {
withZlibBufferSize(8192) { // use smaller buffer to check.
val str = sampleLongText.repeat(8) // 4000+ chars
println("Input size: ${str.length}")
println("Deflating")
println()
val bytes = str.toByteArray().deflate()
println()
println("Deflated, size = ${bytes.size}, content = ${bytes.toUHexString()}")
println()
println("Inflating")
println()
val inflated = bytes.inflate()
println()
println("Inflated, size = ${inflated.size}, content = ${inflated.toUHexString()}")
println()
assertEquals(str, inflated.decodeToString())
}
}
@Test
fun testInflateBigDataFromJvm() {
withZlibBufferSize(8192) { // use smaller buffer to check.
val input =
"78 9C ED 92 31 8E 1B 31 0C 45 AF C2 2E CD C2 B0 8B 5D 20 65 4A 03 0E B0 57 E0 68 68 0F 61 89 12 44 CA B3 73 FB E5 C8 09 E2 23 A4 50 27 41 E4 E7 E7 D7 BB E4 4A 09 CE 45 5B 02 56 50 4E 25 6E 30 B7 94 36 30 FA 32 C8 57 B0 85 A0 54 16 63 B9 01 CA 0C B6 15 52 B2 7E 67 99 9B 5A DD 0E 70 79 91 5A 50 61 22 92 DE FB B7 E4 87 EB 9B F7 63 9D 5F 27 D0 83 AA 0F 96 40 BD FA F4 7E 3C EA 1B AC 8B 77 A3 40 93 BB E4 55 9E 06 BC D0 72 BE 03 C2 0D 63 A4 AD BB 73 33 DD 95 86 8A 69 8A 34 03 9B 97 41 C2 BB 3F 3C DF B5 50 E0 E4 8A 93 B7 1F E0 6C DD A1 B6 FA E0 87 37 48 F6 45 C5 17 BF FA 15 02 89 B5 CA E4 2E A6 66 80 51 73 77 16 09 8B 2F E3 D2 14 29 58 CD C2 E1 35 8B 37 F0 00 90 65 8F 85 54 5D 85 DD E5 E6 2B 84 05 E5 46 73 1F BC FA E0 92 4B 8B 58 59 77 B3 CF 90 4E 3F 3F 8E 0A 2B DB D2 AF D5 47 A0 D2 BE E0 85 AC FA D1 40 17 22 53 08 59 EC CF 94 D7 C8 0B AA E2 6D 37 BD 87 91 FC C5 45 F6 55 DC 41 97 9D 49 EF 96 0B 94 36 45 D6 65 EF D7 7C B5 15 BD 32 B2 67 F5 2B FA 3F C1 A7 8B FC F6 E8 AA 3B 0B B1 CD 7B 9D FF 90 72 16 ED 76 FE CD 3C 5C 06 3D 83 9E 41 CF A0 67 D0 33 E8 19 F4 0C 7A 06 3D 83 9E 41 CF A0 67 D0 33 E8 19 F4 FC 87 F4 7C 03 ED CB 93 EB"
.hexToBytes()
println()
val bytes = input.inflate()
println("Inflated, size = ${bytes.size}, content = ${bytes.toUHexString()}")
println()
val expected = sampleLongText.repeat(8)
val actual = bytes.decodeToString()
println(expected)
println(actual)
assertEquals(expected.length, actual.length)
assertEquals(expected, actual)
}
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertFalse
internal sealed class MapTest(
private val map: MutableMap<Int, Int>
) {
class ConcurrentMapTest : MapTest(ConcurrentHashMap())
@Test
fun `initial state test`() {
assertEquals(0, map.size)
assertEquals(null, map[1])
assertFalse(map.iterator().hasNext())
assertFails { map.iterator().next() }
}
@Test
fun `get set size`() {
assertEquals(0, map.size)
assertEquals(null, map[1])
map[1] = 2
assertEquals(2, map[1])
assertEquals(1, map.size)
map[2] = 2
assertEquals(2, map[2])
assertEquals(2, map.size)
}
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
internal class TimeUtilsTest {
@Test
fun `can get currentTimeMillis`() {
val time = currentTimeMillis()
assertTrue(time.toString()) { time > 1654209523269 }
}
@Test
fun `can get currentTimeFormatted`() {
// 2022-28-26 18:28:28
assertTrue { currentTimeFormatted().matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$""")) }
}
@Test
fun `can parse explicit timestamp`() {
val epochMilli = 1681174590123 // 2023-04-11 00:56:30 GMT
val regex = Regex("""^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$""")
val formatted = regex.find(formatTime(epochMilli, null))
assertNotNull(formatted)
formatted.groupValues.run {
assertEquals(get(1), "2023")
assertEquals(get(2), "04")
assertTrue { get(3) == "11" || get(3) == "10" }
assertTrue { get(4).toInt() in 0..23 }
assertEquals(get(5), "56")
assertEquals(get(6), "30")
}
}
@Test
fun `can format with custom formatter`() {
fun formatTimeAndPrint(formatter: String?): String {
return formatTime(currentTimeMillis(), formatter).also { println("custom formatted time: $it") }
}
assertTrue {
formatTimeAndPrint("MmMm").matches(Regex("""^MmMm$"""))
}
assertTrue {
formatTimeAndPrint("MM-mm").matches(Regex("""^\d{2}-\d{2}$"""))
}
assertTrue {
formatTimeAndPrint("yyyyMMddHHmmss").matches(Regex("""^\d{14}$"""))
}
assertTrue {
formatTimeAndPrint("yyyyMMddHHmmSS").matches(Regex("""^\d{12}SS$"""))
}
assertTrue {
formatTimeAndPrint(null).matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$"""))
}
assertTrue {
formatTimeAndPrint("yyyy-MM-dd 114514").matches(Regex("""^\d{4}-\d{2}-\d{2} 114514$"""))
}
assertTrue {
formatTimeAndPrint("yyyyMM-114 514--mm-SS").matches(Regex("""^\d{4}\d{2}-114 514--\d{2}-SS$"""))
}
assertTrue {
formatTimeAndPrint("yyyy-MM-dd HH-mm-ss").matches(Regex("""^\d{4}-\d{2}-\d{2} \d{2}-\d{2}-\d{2}$"""))
}
assertTrue {
formatTimeAndPrint("yyyy/MM\\dd HH:mm-ss").matches(Regex("""^\d{4}/\d{2}\\\d{2} \d{2}:\d{2}-\d{2}$"""))
}
}
}

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils

View File

@ -1,187 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import io.ktor.utils.io.core.*
import io.ktor.utils.io.errors.*
import kotlinx.cinterop.*
import platform.posix.*
private fun readlink(path: String): String = memScoped {
val len = realpath(path, null)
if (len != null) {
try {
return len.toKString()
} finally {
free(len)
}
} else {
when (val errno = errno) {
ENOTDIR -> return@memScoped path
EACCES -> return@memScoped path // permission denied
ENOENT -> return@memScoped path // no such file
else -> throw IllegalArgumentException(
"Invalid path($errno): $path",
cause = PosixException.forErrno(posixFunctionName = "realpath()")
)
}
}
}
internal actual class MiraiFileImpl actual constructor(
override val path: String,
) : MiraiFile {
actual companion object {
private const val SEPARATOR = '/'
private val ROOT by lazy { MiraiFileImpl("/") }
@Suppress("UnnecessaryOptInAnnotation")
@OptIn(UnsafeNumber::class)
actual fun getWorkingDir(): MiraiFile {
val path = memScoped {
ByteArray(PATH_MAX).usePinned {
getcwd(it.addressOf(0), it.get().size.convert())
it.get().toKString()
}
}
return MiraiFile.create(path)
}
}
override val absolutePath: String by lazy { kotlin.run { readlink(path) } }
override val parent: MiraiFile? by lazy {
val absolutePath = absolutePath
val p = absolutePath.substringBeforeLast(SEPARATOR, "")
if (p.isEmpty()) {
if (absolutePath.singleOrNull() == SEPARATOR) return@lazy null // root
else return@lazy ROOT
}
MiraiFileImpl(p)
}
override val name: String
get() = absolutePath.substringAfterLast('/', "").ifEmpty { absolutePath }
init {
absolutePath.split('/').forEach { checkName(it) }
}
private fun checkName(name: String) {
name.forEach { c ->
if (c in """\/:?*"><|""") {
throw IllegalArgumentException("'${name}' contains illegal character '$c'.")
}
}
}
override val length: Long
get() = useStat { it.st_size.convert() } ?: 0
@OptIn(UnsafeNumber::class)
override val isFile: Boolean
get() = useStat { it.st_mode.convert<UInt>() flag S_IFREG } ?: false
@OptIn(UnsafeNumber::class)
override val isDirectory: Boolean
get() = useStat { it.st_mode.convert<UInt>() flag S_IFDIR } ?: false
override fun exists(): Boolean = useStat { true } ?: false
override fun resolve(path: String): MiraiFile {
when (path) {
"." -> return this
".." -> return parent ?: this // root
}
if (path.startsWith(SEPARATOR)) {
return MiraiFileImpl(path)
}
return MiraiFileImpl("$absolutePath/$path")
}
override fun resolve(file: MiraiFile): MiraiFile {
val parent = file.parent ?: return resolve(file.name)
return resolve(parent).resolve(file.name)
}
override fun createNewFile(): Boolean {
memScoped {
val fp = fopen(absolutePath, "w")
fwrite(fp, 0, 0, fp)
fclose(fp)
return true
}
}
override fun delete(): Boolean {
return if (isFile) {
remove(absolutePath) == 0
} else {
rmdir(absolutePath) == 0
}
}
override fun mkdir(): Boolean {
@Suppress("UnnecessaryOptInAnnotation") // bug
@OptIn(UnsafeNumber::class)
return (mkdir("$absolutePath/", "755".toUShort(8).convert()).convert<Int>() == 0)
}
@OptIn(UnsafeNumber::class)
override fun mkdirs(): Boolean {
val flags = useStat { it.st_mode.convert<UInt>() }
return when {
flags == null -> {
this.parent?.mkdirs()
mkdir()
}
flags flag S_IFDIR -> {
false // already exists
}
else -> {
mkdir()
}
}
}
override fun input(): Input {
val handle = fopen(absolutePath, "rb")
?: throw IOException(
"Failed to open file '$absolutePath'",
PosixException.forErrno(posixFunctionName = "fopen()")
)
return PosixInputForFile(handle)
}
override fun output(): Output {
val handle = fopen(absolutePath, "wb")
?: throw IOException(
"Failed to open file '$absolutePath'",
PosixException.forErrno(posixFunctionName = "fopen()")
)
return PosixFileInstanceOutput(handle)
}
override fun hashCode(): Int {
return this.path.hashCode()
}
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (!isSameType(this, other)) return false
return this.path == other.path
}
override fun toString(): String {
return "MiraiFileImpl($path)"
}
}

View File

@ -1,18 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlinx.cinterop.UnsafeNumber
import kotlinx.cinterop.convert
import platform.posix._SC_NPROCESSORS_ONLN
import platform.posix.sysconf
@OptIn(UnsafeNumber::class)
public actual fun availableProcessors(): Int = sysconf(_SC_NPROCESSORS_ONLN).convert()

View File

@ -1,49 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.utils
import kotlin.math.absoluteValue
import kotlin.random.Random
import kotlin.test.Test
import kotlin.test.assertEquals
internal class UnixMiraiFileImplTest : AbstractNativeMiraiFileImplTest() {
private val rand = Random.nextInt().absoluteValue
override val baseTempDir: MiraiFile by lazy { MiraiFile.create(MiraiFile.getWorkingDir().absolutePath + "/mirai_unit_tests") }
override val tempPath by lazy { "${baseTempDir.absolutePath}/temp$rand" }
@Test
override fun parent() {
assertEquals(baseTempDir.absolutePath, tempDir.parent!!.absolutePath)
assertEquals(null, MiraiFile.create("/").parent)
assertEquals("/", MiraiFile.create("/dev").parent?.path)
assertEquals("/", MiraiFile.create("/dev").parent?.absolutePath)
super.parent()
}
@Test
override fun `canonical paths for non-canonical input`() {
super.`canonical paths for non-canonical input`()
// extra /sss/..
MiraiFile.create("$tempPath/sss/..").resolve("test").let {
assertPathEquals("${tempPath}/sss/../test", it.path) // because file is not found
assertPathEquals("${tempPath}/sss/../test", it.absolutePath)
}
}
@Test
override fun `resolve absolute`() {
MiraiFile.create("$tempPath/").resolve("/Users").let {
assertEquals("/Users", it.path)
assertEquals("/Users", it.absolutePath)
}
}
}

View File

@ -10,8 +10,6 @@
@file:Suppress("UNUSED_VARIABLE")
import BinaryCompatibilityConfigurator.configureBinaryValidators
import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractNativeLibrary
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import shadow.relocateCompileOnly
import shadow.relocateImplementation
@ -32,8 +30,6 @@ kotlin {
apply(plugin = "explicit-api")
configureJvmTargetsHierarchical("net.mamoe.mirai.internal")
configureNativeTargetsHierarchical(project)
configureNativeTargetBinaries(project) // register native binaries for mirai-core only
optInForAllSourceSets("net.mamoe.mirai.utils.MiraiExperimentalApi")
optInForAllSourceSets("net.mamoe.mirai.utils.MiraiInternalApi")
@ -122,11 +118,6 @@ kotlin {
}
}
findByName("nativeMain")?.apply {
dependencies {
}
}
// Kt bignum
findByName("jvmBaseMain")?.apply {
@ -150,75 +141,12 @@ kotlin {
relocateImplementation(`ktor-client-core_relocated`)
}
}
configure(NATIVE_TARGETS.map { getByName(it + "Main") }
+ NATIVE_TARGETS.map { getByName(it + "Test") }) {
// no relocation in native, include binaries
dependencies {
api(`ktor-io`) {
exclude(ExcludeProperties.`slf4j-api`)
}
}
}
findByName("jvmBaseMain")?.apply {
dependencies {
relocateImplementation(`ktor-client-okhttp_relocated`)
}
}
configure(WIN_TARGETS.map { getByName(it + "Main") }) {
dependencies {
implementation(`ktor-client-curl`)
}
}
configure(LINUX_TARGETS.map { getByName(it + "Main") }) {
dependencies {
implementation(`ktor-client-cio`)
}
}
findByName("darwinMain")?.apply {
dependencies {
implementation(`ktor-client-darwin`)
}
}
// Linkage
NATIVE_TARGETS.forEach { targetName ->
val defFile = projectDir.resolve("src/nativeMain/cinterop/OpenSSL.def")
val target = targets.getByName(targetName) as KotlinNativeTarget
target.compilations.getByName("main").cinterops.create("OpenSSL")
.apply {
this.defFile = defFile
packageName("openssl")
}
configure(target.binaries.filterIsInstance<AbstractNativeLibrary>()) {
export(project(":mirai-core-api"))
export(project(":mirai-core-utils"))
}
}
UNIX_LIKE_TARGETS.forEach { target ->
(targets.getByName(target) as KotlinNativeTarget).compilations.getByName("main").cinterops.create("Socket")
.apply {
defFile = projectDir.resolve("src/unixMain/cinterop/Socket.def")
packageName("sockets")
}
}
WIN_TARGETS.forEach { target ->
(targets.getByName(target) as KotlinNativeTarget).compilations.getByName("main").cinterops.create("Socket")
.apply {
defFile = projectDir.resolve("src/mingwX64Main/cinterop/Socket.def")
packageName("sockets")
}
}
disableCrossCompile()
// val unixMain by getting {
// dependencies {
// implementation(`ktor-client-cio`)
// }
// }
}
}
@ -226,28 +154,6 @@ atomicfu {
transformJvm = false
}
afterEvaluate {
val main = projectDir.resolve("src/nativeTest/kotlin/local/TestMain.kt")
if (!main.exists()) {
main.writeText(
"""
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.local
fun main() {}
""".trimIndent()
)
}
}
if (tasks.findByName("androidMainClasses") != null) {
tasks.register("checkAndroidApiLevel") {
doFirst {

View File

@ -84,7 +84,6 @@ internal class QRCodeLoginProcessorImpl(
margin = qrCodeLoginListener.qrCodeMargin,
ecLevel = qrCodeLoginListener.qrCodeEcLevel,
),
attempts = 1
)
check(resp is WtLogin.TransEmp.Response.FetchQRCode) { "Cannot fetch qrcode, resp=$resp" }
qrCodeLoginListener.onFetchQRCode(handler.context.bot, resp.imageData)

View File

@ -1,24 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmName("NativeTestWrapperKt")
package net.mamoe.mirai.internal.test
import kotlin.jvm.JvmName
import kotlin.test.Test
internal class NativeTestWrapper {
@Test
fun test() {
startNativeTestIfNeeded()
}
}
internal expect fun startNativeTestIfNeeded()

View File

@ -15,7 +15,6 @@ expect fun currentPlatform(): Platform
enum class PlatformRuntime {
JVM,
DALVIK,
NATIVE,
}
// see `@DisabledOnPlatform`
@ -33,19 +32,5 @@ sealed class Platform(
object AndroidInstrumentedTest : Android(PlatformRuntime.DALVIK)
object Jvm : JvmLike(PlatformRuntime.JVM)
sealed class Native : Platform(PlatformRuntime.NATIVE)
sealed class Windows : Native()
object MingwX64 : Windows()
sealed class UnixLike : Native()
sealed class Linux : UnixLike()
object LinuxX64 : Linux()
sealed class Macos : UnixLike()
object MacosX64 : Macos()
object MacosArm64 : Macos()
}

View File

@ -1,24 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
import io.ktor.client.plugins.*
internal actual fun createDefaultHttpClient(): HttpClient {
return HttpClient(Darwin) {
install(HttpTimeout) {
this.requestTimeoutMillis = 30_0000
this.connectTimeoutMillis = 30_0000
this.socketTimeoutMillis = 30_0000
}
}
}

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
* Copyright 2019-2023 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
@ -9,4 +9,3 @@
package net.mamoe.mirai.internal.test
internal actual fun startNativeTestIfNeeded() {}

View File

@ -1,24 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.*
internal actual fun createDefaultHttpClient(): HttpClient {
return HttpClient(CIO) {
install(HttpTimeout) {
this.requestTimeoutMillis = 30_0000
this.connectTimeoutMillis = 30_0000
this.socketTimeoutMillis = 30_0000
}
}
}

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

View File

@ -1,10 +0,0 @@
/*
* Copyright 2019-2022 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal

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