mirror of
https://github.com/mamoe/mirai.git
synced 2024-12-26 00:20:10 +08:00
Remove native (#2700)
* Remove native target * Add foojay-resolver-convention * disable windows
This commit is contained in:
parent
9a6b9cc900
commit
8ff64d4a7f
88
.github/codegen/generate-build-native.ws.kts
vendored
88
.github/codegen/generate-build-native.ws.kts
vendored
@ -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")
|
270
.github/workflows/build.yml
vendored
270
.github/workflows/build.yml
vendored
@ -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 }}
|
||||
|
||||
|
4
.github/workflows/check-publishing.yml
vendored
4
.github/workflows/check-publishing.yml
vendored
@ -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') }}
|
||||
|
168
.github/workflows/release.yml
vendored
168
.github/workflows/release.yml
vendored
@ -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
|
||||
|
@ -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>
|
@ -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>
|
@ -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=""net.mamoe.mirai.internal.test.NativeTestWrapper.test""/>
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions"/>
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2"/>
|
||||
</configuration>
|
||||
</component>
|
@ -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=""net.mamoe.mirai.internal.test.NativeTestWrapper.test""/>
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions"/>
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2"/>
|
||||
</configuration>
|
||||
</component>
|
@ -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=""net.mamoe.mirai.internal.test.NativeTestWrapper.test""/>
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions"/>
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2"/>
|
||||
</configuration>
|
||||
</component>
|
@ -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=""net.mamoe.mirai.internal.test.NativeTestWrapper.test""/>
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions"/>
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2"/>
|
||||
</configuration>
|
||||
</component>
|
@ -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=""net.mamoe.mirai.internal.test.NativeTestWrapper.test""/>
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions"/>
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<method v="2"/>
|
||||
</configuration>
|
||||
</component>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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)
|
||||
|
@ -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)
|
@ -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>` 为 macOS,iOS,WatchOS 等 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)
|
||||
|
||||
|
@ -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)。
|
||||
|
@ -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/` 类似路径找到生成的静态链接库和头文件。
|
@ -31,10 +31,6 @@ Gradle 任务:
|
||||
|
||||
查看 [BuildingCoreAndroid](BuildingCoreAndroid.md)。
|
||||
|
||||
### 构建 core 的 Native 目标
|
||||
|
||||
查看 [BuildingCoreNative](BuildingCoreNative.md)。
|
||||
|
||||
## 构建 IntelliJ 插件
|
||||
|
||||
可通过如下命令构建 IntelliJ 平台 IDE 的插件。构建成功的插件将可以在 `mirai-console/tools/intellij-plugin/build/distribution` 中找到。
|
||||
|
@ -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
|
||||
|
@ -22,7 +22,6 @@ kotlin {
|
||||
apply(plugin = "explicit-api")
|
||||
|
||||
configureJvmTargetsHierarchical("net.mamoe.mirai.compiler.annotations")
|
||||
configureNativeTargetsHierarchical(project)
|
||||
}
|
||||
|
||||
configureMppPublishing()
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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)
|
@ -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)
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
@ -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 = "/"
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
@ -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.")
|
||||
}
|
@ -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) }
|
||||
}
|
||||
}
|
@ -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) {
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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 {}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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)
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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)
|
@ -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()
|
@ -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() }
|
||||
)
|
||||
|
||||
|
||||
}
|
@ -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()
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
myrust.h
|
||||
|
||||
*.iml
|
||||
|
||||
src/bindings.rs
|
||||
/*.h
|
@ -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"
|
@ -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");
|
||||
}
|
@ -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"
|
||||
|
@ -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());
|
||||
// }
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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("\\", "/"))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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}$"""))
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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)"
|
||||
}
|
||||
}
|
@ -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()
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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() {}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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
Loading…
Reference in New Issue
Block a user