mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-18 06:02:41 +01:00
Merge branch 'master' into standalone-mode
# Conflicts: # Plan/settings.gradle
This commit is contained in:
commit
3b09fd8da2
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
10
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -12,9 +12,13 @@ labels: 'Bug'
|
||||
### Exceptions & Other Logs
|
||||
<!-- If reporting an Exception, please provide the error log from /plugins/Plan/logs, it has context in it -->
|
||||
|
||||
```
|
||||
Place log contents here
|
||||
<!-- Paste log contents inside the backticks below -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
### Plugin versions
|
||||
<!-- Versions of Plan and any other related plugins -->
|
||||
|
||||
### Additional information
|
||||
<!-- Any additional information, plugin versions, context, what was attempted, etc -->
|
||||
<!-- Any additional information, context, what was attempted, etc -->
|
||||
|
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: Question
|
||||
url: https://github.com/plan-player-analytics/Plan/discussions/new
|
||||
about: Ask a question about the plugin on the Discussions forum
|
||||
- name: Installation help
|
||||
url: https://discordapp.com/invite/yXKmjzT
|
||||
about: Get help for installations on Discord
|
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
10
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Suggestion
|
||||
about: Feature request or an addition
|
||||
---
|
||||
|
||||
### Is your feature request related to a problem? Please describe.
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
### I would like to be able to..
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
15
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
15
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Suggestion
|
||||
description: Feature request or an addition
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: I would like to be able to..
|
||||
description: A clear and concise description of what you want to happen
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: This section helps figure out why this feature is needed - What would this feature be used for? How would you use this feature?
|
||||
validations:
|
||||
required: true
|
16
.github/ISSUE_TEMPLATE/plugin_request.md
vendored
16
.github/ISSUE_TEMPLATE/plugin_request.md
vendored
@ -1,16 +0,0 @@
|
||||
---
|
||||
name: Plugin Suggestion
|
||||
about: Suggest data from another plugin for Plan
|
||||
labels: 'New Data, DataExtensions, Help Wanted'
|
||||
|
||||
---
|
||||
|
||||
### Data to display
|
||||
<!-- List any data you would like to see from the plugin -->
|
||||
|
||||
### Plugin information
|
||||
|
||||
<!-- Required -->
|
||||
**API or Source Code:** [Link](URL HERE)
|
||||
<!-- Required -->
|
||||
**Project page or Downloads:** [Link](URL HERE)
|
20
.github/ISSUE_TEMPLATE/plugin_request.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/plugin_request.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Plugin Suggestion
|
||||
description: Suggest data from another plugin for Plan
|
||||
labels: [New Data, DataExtensions, Help Wanted]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Data to display
|
||||
description: List any data you would like to see from the plugin
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Link to API or Source Code
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Link to Project page or Downloads
|
||||
validations:
|
||||
required: true
|
12
.github/ISSUE_TEMPLATE/question.md
vendored
12
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,12 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a Question about the plugin, see 'Possible Pitfalls' under Projects before asking your question.
|
||||
labels: 'Question'
|
||||
|
||||
---
|
||||
|
||||
<!-- Remove this comment and write your question here -->
|
||||
|
||||
|
||||
### Additional context
|
||||
<!-- Any additional information to understand the question better -->
|
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@ -3,9 +3,13 @@ updates:
|
||||
- package-ecosystem: gradle
|
||||
directory: "/Plan"
|
||||
schedule:
|
||||
interval: daily
|
||||
interval: weekly
|
||||
day: friday
|
||||
open-pull-requests-limit: 99
|
||||
ignore:
|
||||
- dependency-name: net.fabricmc:fabric-loader
|
||||
versions:
|
||||
- ">=0, < 2"
|
||||
- dependency-name: com.djrapitops:Extension-ProtocolSupport
|
||||
versions:
|
||||
- ">= 4.a, < 5"
|
||||
@ -15,3 +19,9 @@ updates:
|
||||
- dependency-name: com.h2database:h2
|
||||
versions:
|
||||
- "> 1.4.199"
|
||||
- package-ecosystem: npm
|
||||
directory: "/Plan/react/dashboard"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: friday
|
||||
open-pull-requests-limit: 99
|
||||
|
96
.github/workflows/ci.yml
vendored
Normal file
96
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test, Build & Upload
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- 3306
|
||||
env:
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_DATABASE: test
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
- name: Setup Selenium Webdriver
|
||||
uses: nanasess/setup-chromedriver@v1
|
||||
- name: Setup Selenium Webdriver settings
|
||||
run: |
|
||||
export DISPLAY=:99
|
||||
chromedriver --url-base=/wd/hub &
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
- name: Verify MariaDB connection
|
||||
env:
|
||||
PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Build jars
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew shadowJar
|
||||
- name: Get versions
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew snapshotVersion jarNameVersion
|
||||
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||
echo "git_hash=$git_hash" >> $GITHUB_ENV
|
||||
echo "snapshotVersion=$(cat build/versions/snapshot.txt)" >> $GITHUB_ENV
|
||||
echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV
|
||||
echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV
|
||||
- name: Upload Plan.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar
|
||||
- name: Upload PlanFabric.jar
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar
|
||||
path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar
|
||||
- name: Test
|
||||
env:
|
||||
MYSQL_DB: test
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASS: password
|
||||
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
CHROMEDRIVER: /usr/local/bin/chromedriver
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew build
|
||||
- name: SonarCloud
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
if: "${{ env.SONAR_TOKEN != '' }}"
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=player-analytics-plan
|
44
.github/workflows/gradle-pr.yml
vendored
44
.github/workflows/gradle-pr.yml
vendored
@ -1,44 +0,0 @@
|
||||
|
||||
name: Java PR CI
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- 3306
|
||||
env:
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_DATABASE: test
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.11
|
||||
- name: Verify MariaDB connection
|
||||
env:
|
||||
PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
MYSQL_DB: test
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASS: password
|
||||
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew build
|
51
.github/workflows/gradle.yml
vendored
51
.github/workflows/gradle.yml
vendored
@ -1,51 +0,0 @@
|
||||
|
||||
name: Java CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- 3306
|
||||
env:
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASSWORD: password
|
||||
MYSQL_DATABASE: test
|
||||
MYSQL_ROOT_PASSWORD: password
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 1.11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.11
|
||||
- name: Verify MariaDB connection
|
||||
env:
|
||||
PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
|
||||
sleep 1
|
||||
done
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
MYSQL_DB: test
|
||||
MYSQL_USER: user
|
||||
MYSQL_PASS: password
|
||||
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew build
|
||||
- name: SonarCloud
|
||||
run: |
|
||||
cd Plan
|
||||
./gradlew sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=player-analytics-plan
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
30
.github/workflows/html.yml
vendored
30
.github/workflows/html.yml
vendored
@ -1,30 +0,0 @@
|
||||
# This workflow will build a Java project with Gradle
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Update html-branch
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1
|
||||
- name: Get git TAG
|
||||
id: tagName
|
||||
uses: olegtarasov/get-tag@v2.1
|
||||
- name: Copy assets 🔧
|
||||
run: |
|
||||
mkdir -p workingdir/Plan/src/main/resources/assets/plan
|
||||
cp -r Plan/common/src/main/resources/assets/plan workingdir/Plan/src/main/resources/assets/plan
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||
with:
|
||||
branch: html # The branch the action should deploy to.
|
||||
folder: workingdir # The folder the action should deploy.
|
||||
commit-message: ${{ steps.tagName.outputs.tag }}
|
28
.github/workflows/issues.yml
vendored
Normal file
28
.github/workflows/issues.yml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
name: Issue Automation
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
|
||||
# map fields with customized labels
|
||||
env:
|
||||
inbox: INBOX
|
||||
refinement: Needs Refinement
|
||||
refined: Refined & Actionable
|
||||
assigned: Assigned to Milestone
|
||||
done: Done
|
||||
|
||||
jobs:
|
||||
issue_opened:
|
||||
name: New Issue Opened
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'issues' && github.event.action == 'opened'
|
||||
steps:
|
||||
- name: Add issue to ${{ env.inbox }}
|
||||
uses: leonsteinhaeuser/project-beta-automations@v1.2.1
|
||||
with:
|
||||
gh_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
organization: plan-player-analytics
|
||||
project_id: 3
|
||||
resource_node_id: ${{ github.event.issue.node_id }}
|
||||
status_value: ${{ env.inbox }} # Target status
|
8
.github/workflows/javadocs.yml
vendored
8
.github/workflows/javadocs.yml
vendored
@ -16,6 +16,11 @@ jobs:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1
|
||||
|
||||
- name: Set up JDK 🍵
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '17'
|
||||
- name: Build Javadocs 🔧
|
||||
run: |
|
||||
cd Plan
|
||||
@ -31,8 +36,9 @@ jobs:
|
||||
cp scripts/index.html javadocs/index.html
|
||||
cd javadocs
|
||||
touch .nojekyll
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.4
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: javadocs # The folder the action should deploy.
|
||||
|
137
.github/workflows/on-release.yml
vendored
Normal file
137
.github/workflows/on-release.yml
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
update_versions_txt:
|
||||
name: Update versions.txt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1
|
||||
- name: Get Download URL
|
||||
run: |
|
||||
curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt
|
||||
jq -r '.url' asset.txt > url.txt
|
||||
echo "RELEASE_DOWNLOAD_URL=$(cat url.txt)" >> $GITHUB_ENV
|
||||
- name: Write REL release line
|
||||
if: ${{ github.event.release.prerelease == false }}
|
||||
run: |
|
||||
echo "REL|${{ github.event.release.tag_name }}|${{ env.RELEASE_DOWNLOAD_URL }}|https://github.com/plan-player-analytics/Plan/releases" > release_line.txt
|
||||
- name: Write DEV release line
|
||||
if: ${{ github.event.release.prerelease == true }}
|
||||
run: |
|
||||
echo "DEV|${{ github.event.release.tag_name }}|${{ env.RELEASE_DOWNLOAD_URL }}|${{ github.event.release.html_url }}" > release_line.txt
|
||||
- name: Append to versions.txt
|
||||
run: |
|
||||
cat versions.txt > temp.txt
|
||||
cat release_line.txt temp.txt > versions.txt
|
||||
- name: Commit and push changes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
committer_name: GitHub Actions
|
||||
committer_email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
message: Update versions.txt ${{ github.event.release.name }}
|
||||
add: versions.txt
|
||||
|
||||
update_html:
|
||||
name: Update html-branch
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2.3.1
|
||||
- name: Get git TAG
|
||||
id: tagName
|
||||
uses: olegtarasov/get-tag@v2.1
|
||||
- name: Copy assets 🔧
|
||||
run: |
|
||||
mkdir -p workingdir/Plan/src/main/resources/assets/plan
|
||||
mkdir -p workingdir/react/dashboard
|
||||
cp -r Plan/common/src/main/resources/assets/plan workingdir/Plan/src/main/resources/assets/plan
|
||||
cp -r Plan/react/dashboard workingdir/react/dashboard
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@4.0.0
|
||||
with:
|
||||
branch: html # The branch the action should deploy to.
|
||||
folder: workingdir # The folder the action should deploy.
|
||||
commit-message: ${{ steps.tagName.outputs.tag }}
|
||||
|
||||
upload_release_ore:
|
||||
name: Ore Upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download release artifact for upload
|
||||
run: |
|
||||
curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric") | not)' > asset.txt
|
||||
jq -r '.url' asset.txt > url.txt
|
||||
jq -r '.name' asset.txt > name.txt
|
||||
wget -i url.txt
|
||||
echo "JAR_FILENAME=$(cat name.txt)" >> $GITHUB_ENV
|
||||
- name: Upload artifact for ore upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.event.release.name }}
|
||||
path: ${{ env.JAR_FILENAME }}
|
||||
- name: Upload release to Ore 🚀
|
||||
if: ${{ github.event.release.prerelease == false }}
|
||||
uses: dualspiral/ore-upload-action@v1
|
||||
with:
|
||||
plugin: ${{ github.event.release.name }}
|
||||
description: ${{ github.event.release.body }}
|
||||
apiKey: ${{ secrets.ORE_API_TOKEN }}
|
||||
channel: Release
|
||||
pluginId: plan
|
||||
createForumPost: true
|
||||
- name: Upload DEV release to Ore 🚀
|
||||
if: ${{ github.event.release.prerelease == true }}
|
||||
uses: dualspiral/ore-upload-action@v1
|
||||
with:
|
||||
plugin: ${{ github.event.release.name }}
|
||||
description: ${{ github.event.release.body }}
|
||||
apiKey: ${{ secrets.ORE_API_TOKEN }}
|
||||
channel: DEV
|
||||
pluginId: plan
|
||||
createForumPost: false
|
||||
|
||||
upload_release_curseforge:
|
||||
name: CurseForge Upload
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download release artifact for upload
|
||||
run: |
|
||||
curl 'https://api.github.com/repos/plan-player-analytics/plan/releases/${{ github.event.release.id }}/assets' | jq -r '.[] | {name: .name, url: .browser_download_url} | select(.url | strings | test("Fabric"))' > asset.txt
|
||||
jq -r '.url' asset.txt > url.txt
|
||||
jq -r '.name' asset.txt > name.txt
|
||||
wget -i url.txt
|
||||
echo "JAR_FILENAME=$(cat name.txt)" >> $GITHUB_ENV
|
||||
- name: Upload release to CurseForge 🚀
|
||||
if: ${{ github.event.release.prerelease == false }}
|
||||
uses: itsmeow/curseforge-upload@master
|
||||
with:
|
||||
token: ${{ secrets.CF_API_TOKEN }}
|
||||
project_id: 508727
|
||||
game_endpoint: minecraft
|
||||
file_path: ${{ env.JAR_FILENAME }}
|
||||
changelog: ${{ github.event.release.body }}
|
||||
changelog_type: markdown
|
||||
display_name: ${{ github.event.release.name }}
|
||||
game_versions: "2:Java 17,73407:1.19,Fabric"
|
||||
release_type: release
|
||||
relations: fabric-api:requiredDependency,luckperms:optionalDependency
|
||||
- name: Upload prerelease to CurseForge 🚀
|
||||
if: ${{ github.event.release.prerelease == true }}
|
||||
uses: itsmeow/curseforge-upload@master
|
||||
with:
|
||||
token: ${{ secrets.CF_API_TOKEN }}
|
||||
project_id: 508727
|
||||
game_endpoint: minecraft
|
||||
file_path: ${{ env.JAR_FILENAME }}
|
||||
changelog: ${{ github.event.release.body }}
|
||||
changelog_type: markdown
|
||||
display_name: ${{ github.event.release.name }}
|
||||
game_versions: "2:Java 17,73407:1.19,Fabric"
|
||||
release_type: beta
|
||||
relations: fabric-api:requiredDependency,luckperms:optionalDependency
|
34
.gitignore
vendored
34
.gitignore
vendored
@ -3,14 +3,18 @@ Plan.iml
|
||||
PlanPluginBridge.iml
|
||||
.sonar/
|
||||
builds/
|
||||
# Nukkit creates server.log during tests for some reason.
|
||||
# Nukkit & Fabric create log files during tests for some reason.
|
||||
server.log
|
||||
/Plan/fabric/logs/
|
||||
|
||||
*.db
|
||||
|
||||
**/.gradle
|
||||
out/
|
||||
|
||||
# Fabric
|
||||
remappedSrc/
|
||||
|
||||
# Created by https://www.gitignore.io/api/maven,eclipse,intellij,netbeans,osx,windows,notepadpp,windows,java
|
||||
|
||||
### Maven ###
|
||||
@ -164,33 +168,6 @@ $RECYCLE.BIN/
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### NotepadPP ###
|
||||
# Notepad++ backups #
|
||||
*.bak
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Java ###
|
||||
*.class
|
||||
|
||||
@ -203,3 +180,4 @@ $RECYCLE.BIN/
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
/.vs
|
||||
|
@ -34,7 +34,7 @@ This Code of Conduct applies both within project spaces and in public spaces whe
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team on discord https://discordapp.com/invite/yXKmjzT (you can send a DM to Rsl1122). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team on discord https://discordapp.com/invite/yXKmjzT (you can send a DM to AuroraLS3). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
|
@ -4,7 +4,11 @@ dependencies {
|
||||
compileOnly "com.google.code.gson:gson:$gsonVersion"
|
||||
}
|
||||
|
||||
ext.apiVersion = '5.4-R0.1'
|
||||
compileJava {
|
||||
options.release = 8
|
||||
}
|
||||
|
||||
ext.apiVersion = '5.5-R0.1'
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
|
@ -39,28 +39,22 @@ import java.util.function.Predicate;
|
||||
public final class CompositeResolver implements Resolver {
|
||||
|
||||
private final List<String> prefixes;
|
||||
private final List<Function<Request, Optional<Response>>> resolvers;
|
||||
private final List<Predicate<Request>> canAccess;
|
||||
private final List<Resolver> resolvers;
|
||||
|
||||
CompositeResolver() {
|
||||
this.prefixes = new ArrayList<>();
|
||||
this.resolvers = new ArrayList<>();
|
||||
this.canAccess = new ArrayList<>();
|
||||
}
|
||||
|
||||
public static CompositeResolver.Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private Optional<Function<Request, Optional<Response>>> getResolver(URIPath target) {
|
||||
private Optional<Resolver> getResolver(URIPath target) {
|
||||
return target.getPart(0).flatMap(this::findResolver);
|
||||
}
|
||||
|
||||
private Optional<Predicate<Request>> getAccessCheck(URIPath target) {
|
||||
return target.getPart(0).flatMap(this::findAccessCheck);
|
||||
}
|
||||
|
||||
private Optional<Function<Request, Optional<Response>>> findResolver(String prefix) {
|
||||
private Optional<Resolver> findResolver(String prefix) {
|
||||
for (int i = 0; i < prefixes.size(); i++) {
|
||||
if (prefixes.get(i).equals(prefix)) {
|
||||
return Optional.of(resolvers.get(i));
|
||||
@ -69,21 +63,11 @@ public final class CompositeResolver implements Resolver {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Predicate<Request>> findAccessCheck(String prefix) {
|
||||
for (int i = 0; i < prefixes.size(); i++) {
|
||||
if (prefixes.get(i).equals(prefix)) {
|
||||
return Optional.of(canAccess.get(i));
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
void add(String prefix, Resolver resolver) {
|
||||
if (prefix == null) throw new IllegalArgumentException("Prefix can not be null");
|
||||
if (resolver == null) throw new IllegalArgumentException("Resolver can not be null");
|
||||
prefixes.add(prefix);
|
||||
resolvers.add(resolver::resolve);
|
||||
canAccess.add(resolver::canAccess);
|
||||
resolvers.add(resolver);
|
||||
}
|
||||
|
||||
void add(String prefix, Function<Request, Response> resolver, Predicate<Request> accessCheck) {
|
||||
@ -93,22 +77,27 @@ public final class CompositeResolver implements Resolver {
|
||||
}
|
||||
if (accessCheck == null) throw new IllegalArgumentException("Predicate<Request> accessCheck can not be null");
|
||||
prefixes.add(prefix);
|
||||
resolvers.add(request -> Optional.ofNullable(resolver.apply(request)));
|
||||
canAccess.add(accessCheck);
|
||||
resolvers.add(new FunctionalResolverWrapper(request -> Optional.ofNullable(resolver.apply(request)), accessCheck));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
Request forThis = request.omitFirstInPath();
|
||||
return getAccessCheck(forThis.getPath())
|
||||
.map(resolver -> resolver.test(forThis))
|
||||
return getResolver(forThis.getPath())
|
||||
.map(resolver -> resolver.canAccess(forThis))
|
||||
.orElse(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
Request forThis = request.omitFirstInPath();
|
||||
return getResolver(forThis.getPath()).flatMap(resolver -> resolver.apply(forThis));
|
||||
return getResolver(forThis.getPath()).flatMap(resolver -> resolver.resolve(forThis));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresAuth(Request request) {
|
||||
Request forThis = request.omitFirstInPath();
|
||||
return getResolver(forThis.getPath()).map(resolver -> resolver.requiresAuth(forThis)).orElse(true);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web.resolver;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class FunctionalResolverWrapper implements Resolver {
|
||||
|
||||
private final Function<Request, Optional<Response>> resolver;
|
||||
private final Predicate<Request> accessCheck;
|
||||
|
||||
public FunctionalResolverWrapper(Function<Request, Optional<Response>> resolver, Predicate<Request> accessCheck) {
|
||||
this.resolver = resolver;
|
||||
this.accessCheck = accessCheck;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canAccess(Request request) {
|
||||
return accessCheck.test(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return resolver.apply(request);
|
||||
}
|
||||
}
|
@ -66,6 +66,11 @@ public class ResponseBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected ResponseBuilder removeHeader(String header) {
|
||||
response.headers.remove(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for building redirects.
|
||||
*
|
||||
@ -103,7 +108,8 @@ public class ResponseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
return setContent(content.getBytes(charset));
|
||||
return setContent(content.getBytes(charset))
|
||||
.removeHeader("Accept-Ranges"); // Can compress
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,25 +34,30 @@ public final class Request {
|
||||
private final URIQuery query;
|
||||
private final WebUser user;
|
||||
private final Map<String, String> headers;
|
||||
private final byte[] requestBody;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param method HTTP method, GET, PUT, POST, etc
|
||||
* @param path Requested path /example/target
|
||||
* @param query Request parameters ?param=value etc
|
||||
* @param user Web user doing the request (if authenticated)
|
||||
* @param headers Request headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
* @param method HTTP method, GET, PUT, POST, etc
|
||||
* @param path Requested path /example/target
|
||||
* @param query Request parameters ?param=value etc
|
||||
* @param user Web user doing the request (if authenticated)
|
||||
* @param headers Request headers https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
|
||||
* @param requestBody Raw body as bytes, if present
|
||||
*/
|
||||
public Request(String method, URIPath path, URIQuery query, WebUser user, Map<String, String> headers) {
|
||||
public Request(String method, URIPath path, URIQuery query, WebUser user, Map<String, String> headers, byte[] requestBody) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.query = query;
|
||||
this.user = user;
|
||||
this.headers = headers;
|
||||
this.requestBody = requestBody;
|
||||
}
|
||||
|
||||
// Special constructor that figures out URIPath and URIQuery from "/path/and?query=params"
|
||||
/**
|
||||
* Special constructor that figures out URIPath and URIQuery from "/path/and?query=params" and has no request body.
|
||||
*/
|
||||
public Request(String method, String target, WebUser user, Map<String, String> headers) {
|
||||
this.method = method;
|
||||
if (target.contains("?")) {
|
||||
@ -65,6 +70,7 @@ public final class Request {
|
||||
}
|
||||
this.user = user;
|
||||
this.headers = headers;
|
||||
this.requestBody = new byte[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,6 +100,15 @@ public final class Request {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body, if present.
|
||||
*
|
||||
* @return byte[].
|
||||
*/
|
||||
public byte[] getRequestBody() {
|
||||
return requestBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user making the request.
|
||||
*
|
||||
@ -114,7 +129,7 @@ public final class Request {
|
||||
}
|
||||
|
||||
public Request omitFirstInPath() {
|
||||
return new Request(method, path.omitFirst(), query, user, headers);
|
||||
return new Request(method, path.omitFirst(), query, user, headers, requestBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,6 +140,7 @@ public final class Request {
|
||||
", query=" + query +
|
||||
", user=" + user +
|
||||
", headers=" + headers +
|
||||
", body=" + requestBody.length +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,19 +56,35 @@ public final class URIQuery {
|
||||
}
|
||||
String[] keyAndValue = StringUtils.split(kv, "=", 2);
|
||||
if (keyAndValue.length >= 2) {
|
||||
try {
|
||||
parameters.put(
|
||||
URLDecoder.decode(keyAndValue[0], StandardCharsets.UTF_8.name()),
|
||||
URLDecoder.decode(keyAndValue[1], StandardCharsets.UTF_8.name())
|
||||
);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// If UTF-8 is unsupported, we have bigger problems
|
||||
}
|
||||
parseAndPutKeyValuePair(parameters, keyAndValue);
|
||||
} else if (keyAndValue.length == 1) {
|
||||
parseAndPutKeyEmptyValue(parameters, keyAndValue[0]);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private void parseAndPutKeyValuePair(Map<String, String> parameters, String[] keyAndValue) {
|
||||
try {
|
||||
parameters.put(
|
||||
URLDecoder.decode(keyAndValue[0], StandardCharsets.UTF_8.name()),
|
||||
URLDecoder.decode(keyAndValue[1], StandardCharsets.UTF_8.name())
|
||||
);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// If UTF-8 is unsupported, we have bigger problems
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAndPutKeyEmptyValue(Map<String, String> parameters, String s) {
|
||||
try {
|
||||
parameters.put(
|
||||
URLDecoder.decode(s, StandardCharsets.UTF_8.name()), ""
|
||||
);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// If UTF-8 is unsupported, we have bigger problems
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain an URI parameter by key.
|
||||
*
|
||||
@ -80,6 +96,8 @@ public final class URIQuery {
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
if (byKey.isEmpty()) return "";
|
||||
|
||||
StringBuilder builder = new StringBuilder("?");
|
||||
int i = 0;
|
||||
int max = byKey.size();
|
||||
@ -97,8 +115,10 @@ public final class URIQuery {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Map<String, String> copyOfByKey = new HashMap<>(this.byKey);
|
||||
copyOfByKey.remove("password");
|
||||
return "URIQuery{" +
|
||||
"byKey=" + byKey +
|
||||
"byKey=" + copyOfByKey +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -16,29 +16,37 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web.resolver.request;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
public final class WebUser {
|
||||
|
||||
private final String playerName;
|
||||
private final String username;
|
||||
private final UUID playerUUID;
|
||||
private final Set<String> permissions;
|
||||
|
||||
public WebUser(String playerName) {
|
||||
this.playerName = playerName;
|
||||
this.playerUUID = null;
|
||||
this.username = playerName;
|
||||
this.permissions = new HashSet<>();
|
||||
}
|
||||
|
||||
public WebUser(String playerName, String username, Collection<String> permissions) {
|
||||
public WebUser(String playerName, UUID playerUUID, String username, Collection<String> permissions) {
|
||||
this.playerName = playerName;
|
||||
this.playerUUID = playerUUID;
|
||||
this.username = username;
|
||||
this.permissions = new HashSet<>(permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated WebUser now stores the UUID of the linked user
|
||||
*/
|
||||
@Deprecated
|
||||
public WebUser(String playerName, String username, Collection<String> permissions) {
|
||||
this(playerName, null, username, permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated WebUser now has username and player name.
|
||||
*/
|
||||
@ -60,6 +68,14 @@ public final class WebUser {
|
||||
return username;
|
||||
}
|
||||
|
||||
public Optional<UUID> getUUID() {
|
||||
return Optional.ofNullable(playerUUID);
|
||||
}
|
||||
|
||||
public Set<String> getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WebUser{" +
|
||||
|
@ -45,7 +45,6 @@ public final class ExtensionExtractor {
|
||||
private PluginInfo pluginInfo;
|
||||
private List<TabInfo> tabInformation;
|
||||
private List<InvalidateMethod> invalidMethods;
|
||||
private MethodAnnotations methodAnnotations;
|
||||
private Map<ExtensionMethod.ParameterType, ExtensionMethods> methods;
|
||||
private Collection<Method> conditionalMethods;
|
||||
private Collection<Tab> tabAnnotations;
|
||||
@ -102,12 +101,9 @@ public final class ExtensionExtractor {
|
||||
* @deprecated No longer used anywhere, no-op.
|
||||
*/
|
||||
@Deprecated
|
||||
public void extractAnnotationInformation() {
|
||||
// no-op
|
||||
}
|
||||
public void extractAnnotationInformation() {/* no-op */}
|
||||
|
||||
private void extractMethods() {
|
||||
methodAnnotations = new MethodAnnotations();
|
||||
methods = new EnumMap<>(ExtensionMethod.ParameterType.class);
|
||||
methods.put(ExtensionMethod.ParameterType.SERVER_NONE, new ExtensionMethods());
|
||||
methods.put(ExtensionMethod.ParameterType.PLAYER_STRING, new ExtensionMethods());
|
||||
@ -132,55 +128,41 @@ public final class ExtensionExtractor {
|
||||
method.getAnnotation(BooleanProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addBooleanMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), BooleanProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(NumberProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addNumberMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), NumberProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(DoubleProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addDoubleMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), DoubleProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(PercentageProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addPercentageMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), PercentageProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(StringProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addStringMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), StringProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(TableProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addTableMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), TableProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(GroupProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addGroupMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), GroupProvider.class, annotation);
|
||||
});
|
||||
method.getAnnotation(DataBuilderProvider.class).ifPresent(annotation -> {
|
||||
validateMethod(method, annotation);
|
||||
methods.get(method.getParameterType()).addDataBuilderMethod(method);
|
||||
methodAnnotations.put(method.getMethod(), DataBuilderProvider.class, annotation);
|
||||
});
|
||||
|
||||
method.getAnnotation(Conditional.class).ifPresent(annotation -> {
|
||||
conditionalMethods.add(method.getMethod());
|
||||
methodAnnotations.put(method.getMethod(), Conditional.class, annotation);
|
||||
});
|
||||
method.getAnnotation(Tab.class).ifPresent(annotation -> {
|
||||
tabAnnotations.add(annotation);
|
||||
methodAnnotations.put(method.getMethod(), Tab.class, annotation);
|
||||
});
|
||||
method.getAnnotation(Conditional.class).ifPresent(annotation -> conditionalMethods.add(method.getMethod()));
|
||||
method.getAnnotation(Tab.class).ifPresent(tabAnnotations::add);
|
||||
}
|
||||
|
||||
if (methodAnnotations.isEmpty()) {
|
||||
if (methods.values().stream().allMatch(ExtensionMethods::isEmpty)) {
|
||||
throw new IllegalArgumentException(extensionName + " class had no methods annotated with a Provider annotation");
|
||||
}
|
||||
|
||||
@ -440,10 +422,12 @@ public final class ExtensionExtractor {
|
||||
return tabInformation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated During refactoring MethodAnnotations was removed. Using {@link ExtensionExtractor#getMethods()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public MethodAnnotations getMethodAnnotations() {
|
||||
if (methodAnnotations == null) extractMethods();
|
||||
return methodAnnotations;
|
||||
return new MethodAnnotations();
|
||||
}
|
||||
|
||||
public Map<ExtensionMethod.ParameterType, ExtensionMethods> getMethods() {
|
||||
@ -455,4 +439,10 @@ public final class ExtensionExtractor {
|
||||
if (invalidMethods == null) extractInvalidMethods();
|
||||
return invalidMethods;
|
||||
}
|
||||
|
||||
// Visible for testing
|
||||
Collection<Method> getConditionalMethods() {
|
||||
if (conditionalMethods == null) extractMethods();
|
||||
return conditionalMethods;
|
||||
}
|
||||
}
|
||||
|
@ -142,4 +142,15 @@ public class ExtensionMethods {
|
||||
", dataBuilderProviders=" + dataBuilderProviders +
|
||||
'}';
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return booleanProviders.isEmpty()
|
||||
&& numberProviders.isEmpty()
|
||||
&& doubleProviders.isEmpty()
|
||||
&& percentageProviders.isEmpty()
|
||||
&& stringProviders.isEmpty()
|
||||
&& tableProviders.isEmpty()
|
||||
&& groupProviders.isEmpty()
|
||||
&& dataBuilderProviders.isEmpty();
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,17 @@ import java.util.Optional;
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public enum Family {
|
||||
/**
|
||||
* 'fas' (solid) Font awesome family.
|
||||
*/
|
||||
SOLID,
|
||||
/**
|
||||
* 'far' (regular) Font awesome family.
|
||||
*/
|
||||
REGULAR,
|
||||
/**
|
||||
* 'fab' (brand) Font awesome family.
|
||||
*/
|
||||
BRAND;
|
||||
|
||||
public static Optional<Family> getByName(String name) {
|
||||
|
@ -25,6 +25,9 @@ package com.djrapitops.plan.extension.icon;
|
||||
*/
|
||||
public class Icon {
|
||||
|
||||
// Implementation detail, set during icon storage to optimize relation inserts.
|
||||
int id;
|
||||
|
||||
private Family type;
|
||||
private String name;
|
||||
private Color color;
|
||||
|
@ -48,6 +48,7 @@ public final class Table {
|
||||
|
||||
private final String[] columns;
|
||||
private final Icon[] icons;
|
||||
private final TableColumnFormat[] tableColumnFormats;
|
||||
|
||||
private final List<Object[]> rows;
|
||||
|
||||
@ -56,6 +57,10 @@ public final class Table {
|
||||
|
||||
columns = new String[5];
|
||||
icons = new Icon[5];
|
||||
tableColumnFormats = new TableColumnFormat[]{
|
||||
TableColumnFormat.NONE, TableColumnFormat.NONE, TableColumnFormat.NONE,
|
||||
TableColumnFormat.NONE, TableColumnFormat.NONE
|
||||
};
|
||||
rows = new ArrayList<>();
|
||||
}
|
||||
|
||||
@ -91,6 +96,10 @@ public final class Table {
|
||||
return rows;
|
||||
}
|
||||
|
||||
public TableColumnFormat[] getTableColumnFormats() {
|
||||
return tableColumnFormats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating new {@link Table} objects.
|
||||
*/
|
||||
@ -171,6 +180,61 @@ public final class Table {
|
||||
return column(4, columnName, icon);
|
||||
}
|
||||
|
||||
private Factory columnFormat(int index, TableColumnFormat tableColumnFormat) {
|
||||
building.tableColumnFormats[index] = tableColumnFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatting to column one.
|
||||
*
|
||||
* @param tableColumnFormat Format to apply
|
||||
* @return Factory.
|
||||
*/
|
||||
public Factory columnOneFormat(TableColumnFormat tableColumnFormat) {
|
||||
return columnFormat(0, tableColumnFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatting to column two.
|
||||
*
|
||||
* @param tableColumnFormat Format to apply
|
||||
* @return Factory.
|
||||
*/
|
||||
public Factory columnTwoFormat(TableColumnFormat tableColumnFormat) {
|
||||
return columnFormat(1, tableColumnFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatting to column three.
|
||||
*
|
||||
* @param tableColumnFormat Format to apply
|
||||
* @return Factory.
|
||||
*/
|
||||
public Factory columnThreeFormat(TableColumnFormat tableColumnFormat) {
|
||||
return columnFormat(2, tableColumnFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatting to column four.
|
||||
*
|
||||
* @param tableColumnFormat Format to apply
|
||||
* @return Factory.
|
||||
*/
|
||||
public Factory columnFourFormat(TableColumnFormat tableColumnFormat) {
|
||||
return columnFormat(3, tableColumnFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply formatting to column five.
|
||||
*
|
||||
* @param tableColumnFormat Format to apply
|
||||
* @return Factory.
|
||||
*/
|
||||
public Factory columnFiveFormat(TableColumnFormat tableColumnFormat) {
|
||||
return columnFormat(4, tableColumnFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row of values to the table.
|
||||
*
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.extension.table;
|
||||
|
||||
public enum TableColumnFormat {
|
||||
/**
|
||||
* String variables to be formatted as links to player page.
|
||||
*/
|
||||
PLAYER_NAME,
|
||||
/**
|
||||
* String variables to be formatted as colored (using § character).
|
||||
*/
|
||||
CHAT_COLORED,
|
||||
/**
|
||||
* Number variables to be formatted as time amount (eg. 1h 30m 25s).
|
||||
*/
|
||||
TIME_MILLISECONDS,
|
||||
/**
|
||||
* Number epoch millisecond to be formatted as date without second indicator.
|
||||
*/
|
||||
DATE_YEAR,
|
||||
/**
|
||||
* Number epoch millisecond to be formatted as date with second indicator.
|
||||
*/
|
||||
DATE_SECOND,
|
||||
/**
|
||||
* Default formatting, no extra formatting applied.
|
||||
*/
|
||||
NONE
|
||||
}
|
@ -265,7 +265,7 @@ class ExtensionExtractorTest {
|
||||
}
|
||||
|
||||
ExtensionExtractor underTest = new ExtensionExtractor(new Extension());
|
||||
assertEquals("Extension.method did not have any associated Provider for Conditional.", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
|
||||
assertEquals("Extension class had no methods annotated with a Provider annotation", assertThrows(IllegalArgumentException.class, underTest::validateAnnotations).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -489,8 +489,13 @@ class ExtensionExtractorTest {
|
||||
|
||||
String expected = Stream.of(extension.getClass().getMethod("method").getAnnotation(Conditional.class))
|
||||
.map(Conditional::value).findFirst().orElseThrow(AssertionError::new);
|
||||
String result = underTest.getMethodAnnotations().getAnnotations(Conditional.class).stream()
|
||||
.map(Conditional::value).findFirst().orElseThrow(AssertionError::new);
|
||||
String result = underTest.getConditionalMethods().stream()
|
||||
.map(method -> new ExtensionMethod(extension, method))
|
||||
.map(extensionMethod -> extensionMethod.getAnnotation(Conditional.class))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.map(Conditional::value)
|
||||
.findFirst().orElseThrow(AssertionError::new);
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,23 @@
|
||||
import java.nio.file.Files
|
||||
|
||||
// Aggregate Javadocs
|
||||
buildscript {
|
||||
repositories { mavenCentral() }
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.netflix.nebula:gradle-aggregate-javadocs-plugin:3.0.+'
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.github.johnrengelman.shadow" version "7.0.0"
|
||||
id "com.github.johnrengelman.shadow" version "7.1.2" apply false
|
||||
id "java"
|
||||
id 'java-library'
|
||||
id "jacoco"
|
||||
id "checkstyle"
|
||||
id "org.sonarqube" version "3.3"
|
||||
// id "net.ltgt.apt" version "0.21"
|
||||
// id "net.ltgt.apt-idea" version "0.21"
|
||||
id "org.sonarqube" version "3.4.0.2513"
|
||||
id 'fabric-loom' version '0.12.+' apply false
|
||||
}
|
||||
|
||||
apply plugin: 'nebula-aggregate-javadocs'
|
||||
@ -30,13 +34,12 @@ def determineBuildVersion = {
|
||||
def buildVersion = determineBuildVersion()
|
||||
|
||||
allprojects {
|
||||
wrapper.gradleVersion = "7.0.2"
|
||||
|
||||
group "com.djrapitops"
|
||||
version "5.4-SNAPSHOT"
|
||||
version "5.5-SNAPSHOT"
|
||||
|
||||
ext.majorVersion = '5'
|
||||
ext.minorVersion = '4'
|
||||
ext.minorVersion = '5'
|
||||
ext.buildVersion = buildVersion
|
||||
ext.fullVersion = project.ext.majorVersion + '.' + project.ext.minorVersion + ' build ' + project.ext.buildVersion
|
||||
|
||||
@ -52,70 +55,81 @@ subprojects {
|
||||
// Build plugins
|
||||
apply plugin: "com.github.johnrengelman.shadow"
|
||||
apply plugin: "java"
|
||||
apply plugin: "java-library"
|
||||
apply plugin: "maven-publish"
|
||||
|
||||
// Report plugins
|
||||
apply plugin: "checkstyle"
|
||||
apply plugin: "jacoco"
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
compileJava {
|
||||
options.release = 11
|
||||
}
|
||||
|
||||
ext {
|
||||
daggerVersion = "2.37"
|
||||
daggerCompilerVersion = "2.37"
|
||||
daggerVersion = "2.43.2"
|
||||
|
||||
palVersion = "5.0.0"
|
||||
palVersion = "5.1.0"
|
||||
|
||||
bukkitVersion = "1.13.2-R0.1-SNAPSHOT"
|
||||
spigotVersion = "1.13.2-R0.1-SNAPSHOT"
|
||||
paperVersion = "1.13.2-R0.1-SNAPSHOT"
|
||||
spongeVersion = "7.3.0"
|
||||
spongeVersion = "8.1.0"
|
||||
nukkitVersion = "1.0-SNAPSHOT"
|
||||
bungeeVersion = "1.16-R0.4"
|
||||
velocityVersion = "3.0.0-SNAPSHOT"
|
||||
redisBungeeVersion = "0.6.3"
|
||||
redisBungeeVersion = "0.3.8-SNAPSHOT"
|
||||
|
||||
commonsTextVersion = "1.9"
|
||||
commonsCompressVersion = "1.20"
|
||||
commonsCompressVersion = "1.21"
|
||||
commonsCodecVersion = "1.15"
|
||||
caffeineVersion = "3.1.1"
|
||||
mysqlVersion = "8.0.30"
|
||||
jettyVersion = "11.0.11"
|
||||
caffeineVersion = "2.9.2"
|
||||
mysqlVersion = "8.0.25"
|
||||
sqliteVersion = "3.36.0.1"
|
||||
hikariVersion = "4.0.3"
|
||||
slf4jVersion = "1.7.31"
|
||||
geoIpVersion = "2.15.0"
|
||||
gsonVersion = "2.8.7"
|
||||
bstatsVersion = "2.2.1"
|
||||
placeholderapiVersion = "2.10.9"
|
||||
mysqlVersion = "8.0.26"
|
||||
sqliteVersion = "3.39.2.1"
|
||||
hikariVersion = "5.0.1"
|
||||
slf4jVersion = "2.0.0"
|
||||
geoIpVersion = "3.0.1"
|
||||
gsonVersion = "2.9.1"
|
||||
dependencyDownloadVersion = "1.2.1"
|
||||
|
||||
bstatsVersion = "3.0.0"
|
||||
placeholderapiVersion = "2.11.2"
|
||||
nkPlaceholderapiVersion = "1.4-SNAPSHOT"
|
||||
|
||||
junitVersion = "5.9.0"
|
||||
mockitoVersion = "4.8.0"
|
||||
testContainersVersion = "1.17.3"
|
||||
swaggerVersion = "2.2.2"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" } // Spigot
|
||||
maven { url = "https://papermc.io/repo/repository/maven-public/" } // Paper
|
||||
maven { url = "https://repo.spongepowered.org/repository/maven-public/" } // Sponge
|
||||
maven { url = "https://oss.sonatype.org/content/repositories/snapshots" } // BungeeCord
|
||||
maven { url = "https://repo.md-5.net/content/repositories/snapshots/" } // RedisBungee
|
||||
maven { url = "https://repo.velocitypowered.com/snapshots/" } // Velocity
|
||||
maven { url = "https://repo.opencollab.dev/maven-snapshots/" } // Nukkit snapshot
|
||||
maven { url = "https://repo.opencollab.dev/maven-releases/" } // Nukkit release
|
||||
maven { url = "https://repo.codemc.org/repository/maven-public" } // bStats
|
||||
maven { url = "https://repo.playeranalytics.net/releases" } // Plan
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Dependency Injection used across the project
|
||||
implementation "com.google.dagger:dagger:$daggerVersion"
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion"
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerCompilerVersion"
|
||||
shadow "com.google.dagger:dagger:$daggerVersion"
|
||||
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
testImplementation "com.google.dagger:dagger:$daggerVersion"
|
||||
testAnnotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
|
||||
|
||||
// Test Tooling Dependencies
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2' // JUnit 5
|
||||
testImplementation "org.mockito:mockito-core:3.11.2" // Mockito Core
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:3.11.2" // Mockito JUnit 5 Extension
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:3.141.59" // Selenium (Browser tests)
|
||||
testImplementation "com.jayway.awaitility:awaitility:1.7.0" // Awaitility (Concurrent wait conditions)
|
||||
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion" // JUnit 5
|
||||
testImplementation "org.mockito:mockito-core:$mockitoVersion" // Mockito Core
|
||||
testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" // Mockito JUnit 5 Extension
|
||||
testImplementation "com.jayway.awaitility:awaitility:1.7.0"
|
||||
// Awaitility (Concurrent wait conditions)
|
||||
|
||||
// Testing dependencies required by Plan
|
||||
testImplementation "org.xerial:sqlite-jdbc:$sqliteVersion" // SQLite
|
||||
@ -123,7 +137,11 @@ subprojects {
|
||||
}
|
||||
|
||||
configurations {
|
||||
testArtifacts.extendsFrom testRuntimeOnly
|
||||
// Include shadowed dependencies in compile classpath of dependent modules
|
||||
api.extendsFrom shadow
|
||||
|
||||
testArtifacts.extendsFrom testRuntimeOnly // Test classes available to other modules
|
||||
testImplementation.extendsFrom shadow // Include shadowed dependencies in test classpath
|
||||
}
|
||||
// Test classes available to other modules
|
||||
task testJar(type: Jar) {
|
||||
@ -143,13 +161,13 @@ subprojects {
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
toolVersion "8.33"
|
||||
toolVersion "8.44"
|
||||
getConfigDirectory().set file("$rootProject.projectDir/config/checkstyle")
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
xml.required = true
|
||||
// xml.destination file("${buildDir}/jacoco/report.xml")
|
||||
}
|
||||
}
|
||||
@ -163,6 +181,47 @@ sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "Player Analytics"
|
||||
property "sonar.projectKey", "com.djrapitops:Plan"
|
||||
property "sonar.coverage.jacoco.xmlReportPaths", "**/build/report/jacoco/test/jacocoTestReport.xml"
|
||||
property "sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/jacocoTestReport.xml"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PrintSnapshotVersionTask extends DefaultTask {
|
||||
@TaskAction
|
||||
def print() {
|
||||
def versionsDir = project.file("$project.buildDir/versions")
|
||||
def textFile = project.file("$project.buildDir/versions/snapshot.txt")
|
||||
versionsDir.mkdirs()
|
||||
Files.deleteIfExists(textFile.toPath())
|
||||
textFile.createNewFile()
|
||||
textFile << "$project.version"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PrintJarNameVersionTask extends DefaultTask {
|
||||
@TaskAction
|
||||
def print() {
|
||||
def versionsDir = project.file("$project.buildDir/versions")
|
||||
def textFile = project.file("$project.buildDir/versions/jar.txt")
|
||||
versionsDir.mkdirs()
|
||||
Files.deleteIfExists(textFile.toPath())
|
||||
textFile.createNewFile()
|
||||
textFile << "$project.majorVersion.$project.minorVersion-build-$project.buildVersion"
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PrintHumanReadableVersionTask extends DefaultTask {
|
||||
@TaskAction
|
||||
def print() {
|
||||
def versionsDir = project.file("$project.buildDir/versions")
|
||||
def textFile = project.file("$project.buildDir/versions/human.txt")
|
||||
versionsDir.mkdirs()
|
||||
Files.deleteIfExists(textFile.toPath())
|
||||
textFile.createNewFile()
|
||||
textFile << "$project.fullVersion"
|
||||
}
|
||||
}
|
||||
|
||||
// Create a task using the task type
|
||||
tasks.register('snapshotVersion', PrintSnapshotVersionTask)
|
||||
tasks.register('jarNameVersion', PrintJarNameVersionTask)
|
||||
tasks.register('humanReadableVersion', PrintHumanReadableVersionTask)
|
||||
|
@ -5,21 +5,22 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly project(":common")
|
||||
implementation project(path: ":common", configuration: 'shadow')
|
||||
compileOnly project(":api")
|
||||
implementation project(":api")
|
||||
implementation project(":common")
|
||||
|
||||
implementation "net.playeranalytics:platform-abstraction-layer-bukkit:$palVersion"
|
||||
implementation "org.bstats:bstats-bukkit:$bstatsVersion"
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-bukkit:$palVersion"
|
||||
shadow "org.bstats:bstats-bukkit:$bstatsVersion"
|
||||
compileOnly "me.clip:placeholderapi:$placeholderapiVersion"
|
||||
|
||||
compileOnly "com.destroystokyo.paper:paper-api:$paperVersion"
|
||||
testImplementation "com.destroystokyo.paper:paper-api:$paperVersion"
|
||||
|
||||
testImplementation "com.destroystokyo.paper:paper-api:$paperVersion"
|
||||
testImplementation project(path: ":common", configuration: 'testArtifacts')
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics'
|
||||
relocate 'org.slf4j', 'plan.org.slf4j'
|
||||
configurations = [project.configurations.shadow]
|
||||
|
||||
relocate 'org.bstats', 'net.playeranalytics.bstats.utilities.metrics'
|
||||
}
|
@ -35,6 +35,8 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
public class BukkitServerShutdownSave extends ServerShutdownSave {
|
||||
|
||||
private static final String IS_STOPPED = "isStopped";
|
||||
|
||||
@Inject
|
||||
public BukkitServerShutdownSave(
|
||||
Locale locale,
|
||||
@ -57,7 +59,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave {
|
||||
Class<?> minecraftServerClass = Reflection.getMinecraftClass("MinecraftServer");
|
||||
Object minecraftServer = Reflection.getField(minecraftServerClass, "SERVER", minecraftServerClass).get(null);
|
||||
|
||||
return Reflection.getField(minecraftServerClass, "isStopped", boolean.class).get(minecraftServer);
|
||||
return Reflection.getField(minecraftServerClass, IS_STOPPED, boolean.class).get(minecraftServer);
|
||||
} catch (Exception | NoClassDefFoundError | NoSuchFieldError e) {
|
||||
return false;
|
||||
}
|
||||
@ -71,7 +73,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave {
|
||||
Class<?> craftServerClass = Reflection.getCraftBukkitClass("CraftServer");
|
||||
Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer());
|
||||
|
||||
return (Boolean) minecraftServerClass.getMethod("isStopped").invoke(minecraftServer);
|
||||
return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer);
|
||||
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
|
||||
return false;
|
||||
}
|
||||
@ -85,7 +87,7 @@ public class BukkitServerShutdownSave extends ServerShutdownSave {
|
||||
Class<?> craftServerClass = Reflection.getCraftBukkitClass("CraftServer");
|
||||
Object minecraftServer = Reflection.getField(craftServerClass, "console", minecraftServerClass).get(Bukkit.getServer());
|
||||
|
||||
return (Boolean) minecraftServerClass.getMethod("isStopped").invoke(minecraftServer);
|
||||
return (Boolean) minecraftServerClass.getMethod(IS_STOPPED).invoke(minecraftServer);
|
||||
} catch (Exception | NoClassDefFoundError | NoSuchFieldError | NoSuchMethodError e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import com.djrapitops.plan.gathering.ServerShutdownSave;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||
import com.djrapitops.plan.settings.theme.PlanColorScheme;
|
||||
import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import net.playeranalytics.plugin.BukkitPlatformLayer;
|
||||
import net.playeranalytics.plugin.PlatformAbstractionLayer;
|
||||
import net.playeranalytics.plugin.scheduling.RunnableFactory;
|
||||
@ -55,6 +57,7 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
||||
private PluginLogger pluginLogger;
|
||||
private RunnableFactory runnableFactory;
|
||||
private PlatformAbstractionLayer abstractionLayer;
|
||||
private ErrorLogger errorLogger;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
@ -71,7 +74,8 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
||||
.server(getServer())
|
||||
.build();
|
||||
try {
|
||||
system = component.system();
|
||||
system = ThreadContextClassLoaderSwap.performOperation(getClass().getClassLoader(), component::system);
|
||||
errorLogger = component.errorLogger();
|
||||
serverShutdownSave = component.serverShutdownSave();
|
||||
locale = system.getLocaleSystem().getLocale();
|
||||
system.enable();
|
||||
@ -170,42 +174,42 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
||||
pluginLogger.warn("Attempted to register '" + name + "'-command, but it is not in plugin.yml!");
|
||||
continue;
|
||||
}
|
||||
registering.setExecutor(new BukkitCommand(runnableFactory, system.getErrorLogger(), command));
|
||||
registering.setExecutor(new BukkitCommand(runnableFactory, errorLogger, command));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated due to use of APF Config
|
||||
* @deprecated Deprecated due to use of custom Config
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Deprecated(since = "Config.java (2018)")
|
||||
public void reloadConfig() {
|
||||
throw new IllegalStateException("This method should be used on this plugin. Use onReload() instead");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated due to use of APF Config
|
||||
* @deprecated Deprecated due to use of custom Config
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Deprecated(since = "Config.java (2018)")
|
||||
public FileConfiguration getConfig() {
|
||||
throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig() instead");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated due to use of APF Config
|
||||
* @deprecated Deprecated due to use of custom Config
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Deprecated(since = "Config.java (2018)")
|
||||
public void saveConfig() {
|
||||
throw new IllegalStateException("This method should be used on this plugin. Use getMainConfig().save() instead");
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated due to use of APF Config
|
||||
* @deprecated Deprecated due to use of custom Config
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
@Deprecated(since = "Config.java (2018)")
|
||||
public void saveDefaultConfig() {
|
||||
throw new IllegalStateException("This method should be used on this plugin.");
|
||||
}
|
||||
@ -214,4 +218,4 @@ public class Plan extends JavaPlugin implements PlanPlugin {
|
||||
public PlanSystem getSystem() {
|
||||
return system;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import com.djrapitops.plan.modules.bukkit.BukkitPlanModule;
|
||||
import com.djrapitops.plan.modules.bukkit.BukkitServerPropertiesModule;
|
||||
import com.djrapitops.plan.modules.bukkit.BukkitSuperClassBindingModule;
|
||||
import com.djrapitops.plan.modules.bukkit.BukkitTaskModule;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
import net.playeranalytics.plugin.PlatformAbstractionLayer;
|
||||
@ -59,6 +60,8 @@ public interface PlanBukkitComponent {
|
||||
|
||||
ServerShutdownSave serverShutdownSave();
|
||||
|
||||
ErrorLogger errorLogger();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.addons.placeholderapi;
|
||||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import net.playeranalytics.plugin.scheduling.RunnableFactory;
|
||||
import net.playeranalytics.plugin.scheduling.TimeAmount;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
public class PlaceholderCacheRefreshTask extends TaskSystem.Task {
|
||||
|
||||
private final PlanConfig config;
|
||||
|
||||
@Inject
|
||||
public PlaceholderCacheRefreshTask(PlanConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(RunnableFactory runnableFactory) {
|
||||
runnableFactory.create(this).runTaskTimer(
|
||||
TimeAmount.toTicks(60, TimeUnit.SECONDS),
|
||||
TimeAmount.toTicks(15, TimeUnit.SECONDS)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (config.getNode("Plugins.PlaceholderAPI").isEmpty()) {
|
||||
cancel(); // Cancel the task and don't do anything if PlaceholderAPI is not installed.
|
||||
return;
|
||||
}
|
||||
List<String> placeholders = config.getStringList("Plugins.PlaceholderAPI.Load_these_placeholders_on_join");
|
||||
if (placeholders.isEmpty() || placeholders.size() == 1 && placeholders.contains("%plan_server_uuid%")) {
|
||||
// Don't do anything if using default settings or placeholder api is not installed
|
||||
return;
|
||||
}
|
||||
|
||||
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
|
||||
PlaceholderAPI.setPlaceholders(onlinePlayer, placeholders.toString());
|
||||
}
|
||||
}
|
||||
}
|
@ -28,9 +28,7 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ -101,7 +99,7 @@ public class PlanPlaceholderExtension extends PlaceholderExpansion {
|
||||
|
||||
private String getPlaceholderValue(String params, UUID uuid) {
|
||||
try {
|
||||
String value = placeholders.onPlaceholderRequest(uuid, params, Collections.emptyList());
|
||||
String value = placeholders.onPlaceholderRequest(uuid, parseRequest(params), parseParameters(params));
|
||||
|
||||
if ("true".equals(value)) { //hack
|
||||
value = PlaceholderAPIPlugin.booleanTrue();
|
||||
@ -116,6 +114,23 @@ public class PlanPlaceholderExtension extends PlaceholderExpansion {
|
||||
}
|
||||
}
|
||||
|
||||
private String parseRequest(String params) {
|
||||
return params.split(":")[0];
|
||||
}
|
||||
|
||||
private List<String> parseParameters(String params) {
|
||||
List<String> parameters = new ArrayList<>();
|
||||
boolean first = true;
|
||||
for (String parameter : params.split(":")) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
parameters.add(parameter);
|
||||
}
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private String getCached(String params, UUID uuid) {
|
||||
String key = params + "-" + uuid;
|
||||
|
||||
|
@ -24,15 +24,33 @@ import java.util.UUID;
|
||||
|
||||
public class BukkitCMDSender implements CMDSender {
|
||||
|
||||
private static Boolean hasChatAPI = null;
|
||||
|
||||
final CommandSender sender;
|
||||
|
||||
public BukkitCMDSender(CommandSender sender) {
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
protected static synchronized boolean hasBungeeChatAPI() {
|
||||
if (hasChatAPI == null) {
|
||||
try {
|
||||
Class.forName("net.md_5.bungee.api.chat.ComponentBuilder");
|
||||
hasChatAPI = true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
hasChatAPI = false;
|
||||
}
|
||||
}
|
||||
return hasChatAPI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder buildMessage() {
|
||||
return new BukkitPartBuilder(this);
|
||||
if (hasBungeeChatAPI()) {
|
||||
return new BukkitPartBuilder(this);
|
||||
} else {
|
||||
return new ConsoleMessageBuilder(sender::sendMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -53,7 +53,7 @@ public class BukkitPlayerCMDSender extends BukkitCMDSender {
|
||||
|
||||
@Override
|
||||
public boolean supportsChatEvents() {
|
||||
return true;
|
||||
return hasBungeeChatAPI();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.domain;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BukkitPlayerData implements PlatformPlayerData {
|
||||
|
||||
private final Player player;
|
||||
private final String joinAddress;
|
||||
|
||||
public BukkitPlayerData(Player player, String joinAddress) {
|
||||
this.player = player;
|
||||
this.joinAddress = joinAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return player.getUniqueId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getDisplayName() {
|
||||
return Optional.of(player.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> isBanned() {
|
||||
return Optional.of(player.isBanned());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Boolean> isOperator() {
|
||||
return Optional.of(player.isOp());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getJoinAddress() {
|
||||
return Optional.ofNullable(joinAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getCurrentWorld() {
|
||||
return Optional.of(player.getWorld().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getCurrentGameMode() {
|
||||
return Optional.ofNullable(player.getGameMode()).map(Enum::name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Long> getRegisterDate() {
|
||||
return Optional.of(player.getFirstPlayed());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InetAddress> getIPAddress() {
|
||||
return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress);
|
||||
}
|
||||
}
|
@ -28,9 +28,9 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Listener that keeps track of actions that are not considered being AFK.
|
||||
@ -51,7 +51,7 @@ public class BukkitAFKListener implements Listener {
|
||||
@Inject
|
||||
public BukkitAFKListener(PlanConfig config, ErrorLogger errorLogger) {
|
||||
this.errorLogger = errorLogger;
|
||||
this.ignorePermissionInfo = new HashMap<>();
|
||||
this.ignorePermissionInfo = new ConcurrentHashMap<>();
|
||||
|
||||
BukkitAFKListener.assignAFKTracker(config);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import com.djrapitops.plan.delivery.domain.Nickname;
|
||||
import com.djrapitops.plan.gathering.cache.NicknameCache;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.NicknameStoreTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreNicknameTransaction;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -76,7 +76,7 @@ public class ChatListener implements Listener {
|
||||
UUID uuid = player.getUniqueId();
|
||||
String displayName = player.getDisplayName();
|
||||
|
||||
dbSystem.getDatabase().executeTransaction(new NicknameStoreTransaction(
|
||||
dbSystem.getDatabase().executeTransaction(new StoreNicknameTransaction(
|
||||
uuid, new Nickname(displayName, time, serverInfo.getServerUUID()),
|
||||
(playerUUID, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
|
||||
));
|
||||
|
@ -20,6 +20,8 @@ import com.djrapitops.plan.delivery.formatting.EntityNameFormatter;
|
||||
import com.djrapitops.plan.delivery.formatting.ItemNameFormatter;
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.gathering.domain.PlayerKill;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.processing.processors.player.MobKillProcessor;
|
||||
import com.djrapitops.plan.processing.processors.player.PlayerKillProcessor;
|
||||
@ -45,14 +47,17 @@ import java.util.Optional;
|
||||
*/
|
||||
public class DeathEventListener implements Listener {
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final Processing processing;
|
||||
private final ErrorLogger errorLogger;
|
||||
|
||||
@Inject
|
||||
public DeathEventListener(
|
||||
ServerInfo serverInfo,
|
||||
Processing processing,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.serverInfo = serverInfo;
|
||||
this.processing = processing;
|
||||
this.errorLogger = errorLogger;
|
||||
}
|
||||
@ -69,13 +74,13 @@ public class DeathEventListener implements Listener {
|
||||
|
||||
try {
|
||||
Optional<Player> foundKiller = findKiller(dead);
|
||||
if (!foundKiller.isPresent()) {
|
||||
if (foundKiller.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Player killer = foundKiller.get();
|
||||
|
||||
Runnable processor = dead instanceof Player
|
||||
? new PlayerKillProcessor(killer.getUniqueId(), time, dead.getUniqueId(), findWeapon(dead))
|
||||
? new PlayerKillProcessor(getKiller(killer), getVictim((Player) dead), serverInfo.getServerIdentifier(), findWeapon(dead), time)
|
||||
: new MobKillProcessor(killer.getUniqueId());
|
||||
processing.submitCritical(processor);
|
||||
} catch (Exception e) {
|
||||
@ -83,6 +88,14 @@ public class DeathEventListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private PlayerKill.Killer getKiller(Player killer) {
|
||||
return new PlayerKill.Killer(killer.getUniqueId(), killer.getName());
|
||||
}
|
||||
|
||||
private PlayerKill.Victim getVictim(Player victim) {
|
||||
return new PlayerKill.Victim(victim.getUniqueId(), victim.getName());
|
||||
}
|
||||
|
||||
public Optional<Player> findKiller(Entity dead) {
|
||||
EntityDamageEvent entityDamageEvent = dead.getLastDamageCause();
|
||||
if (!(entityDamageEvent instanceof EntityDamageByEntityEvent)) {
|
||||
|
@ -21,7 +21,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.WorldAliasSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreWorldNameTransaction;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -78,7 +78,7 @@ public class GameModeChangeListener implements Listener {
|
||||
String gameMode = event.getNewGameMode().name();
|
||||
String worldName = player.getWorld().getName();
|
||||
|
||||
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
|
||||
dbSystem.getDatabase().executeTransaction(new StoreWorldNameTransaction(serverInfo.getServerUUID(), worldName));
|
||||
worldAliasSettings.addWorld(worldName);
|
||||
|
||||
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(uuid);
|
||||
|
@ -16,29 +16,20 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.listeners.bukkit;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.Nickname;
|
||||
import com.djrapitops.plan.delivery.domain.PlayerName;
|
||||
import com.djrapitops.plan.delivery.domain.ServerName;
|
||||
import com.djrapitops.plan.delivery.export.Exporter;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.gathering.cache.NicknameCache;
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
|
||||
import com.djrapitops.plan.gathering.cache.JoinAddressCache;
|
||||
import com.djrapitops.plan.gathering.domain.BukkitPlayerData;
|
||||
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
|
||||
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
|
||||
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
|
||||
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
|
||||
import com.djrapitops.plan.gathering.listeners.Status;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.ExportSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@ -48,11 +39,7 @@ import org.bukkit.event.player.PlayerLoginEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Event Listener for PlayerJoin, PlayerQuit and PlayerKickEvents.
|
||||
@ -61,61 +48,47 @@ import java.util.function.Supplier;
|
||||
*/
|
||||
public class PlayerOnlineListener implements Listener {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Processing processing;
|
||||
private final PlayerJoinEventConsumer playerJoinEventConsumer;
|
||||
private final PlayerLeaveEventConsumer playerLeaveEventConsumer;
|
||||
private final JoinAddressCache joinAddressCache;
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final DBSystem dbSystem;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final Exporter exporter;
|
||||
private final GeolocationCache geolocationCache;
|
||||
private final NicknameCache nicknameCache;
|
||||
private final SessionCache sessionCache;
|
||||
private final ErrorLogger errorLogger;
|
||||
private final Status status;
|
||||
|
||||
private final Map<UUID, String> joinAddresses;
|
||||
|
||||
@Inject
|
||||
public PlayerOnlineListener(
|
||||
PlanConfig config,
|
||||
Processing processing,
|
||||
PlayerJoinEventConsumer playerJoinEventConsumer,
|
||||
PlayerLeaveEventConsumer playerLeaveEventConsumer,
|
||||
JoinAddressCache joinAddressCache,
|
||||
ServerInfo serverInfo,
|
||||
DBSystem dbSystem,
|
||||
ExtensionSvc extensionService,
|
||||
Exporter exporter,
|
||||
GeolocationCache geolocationCache,
|
||||
NicknameCache nicknameCache,
|
||||
SessionCache sessionCache,
|
||||
Status status,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.config = config;
|
||||
this.processing = processing;
|
||||
this.playerJoinEventConsumer = playerJoinEventConsumer;
|
||||
this.playerLeaveEventConsumer = playerLeaveEventConsumer;
|
||||
this.joinAddressCache = joinAddressCache;
|
||||
this.serverInfo = serverInfo;
|
||||
this.dbSystem = dbSystem;
|
||||
this.extensionService = extensionService;
|
||||
this.exporter = exporter;
|
||||
this.geolocationCache = geolocationCache;
|
||||
this.nicknameCache = nicknameCache;
|
||||
this.sessionCache = sessionCache;
|
||||
this.status = status;
|
||||
this.errorLogger = errorLogger;
|
||||
|
||||
joinAddresses = new HashMap<>();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
public void onPlayerLogin(PlayerLoginEvent event) {
|
||||
try {
|
||||
PlayerLoginEvent.Result result = event.getResult();
|
||||
UUID playerUUID = event.getPlayer().getUniqueId();
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
boolean banned = result == PlayerLoginEvent.Result.KICK_BANNED;
|
||||
boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult();
|
||||
|
||||
String joinAddress = event.getHostname();
|
||||
if (!joinAddress.isEmpty()) {
|
||||
joinAddresses.put(playerUUID, joinAddress.substring(0, joinAddress.lastIndexOf(':')));
|
||||
joinAddress = joinAddress.substring(0, joinAddress.lastIndexOf(':'));
|
||||
joinAddressCache.put(playerUUID, joinAddress);
|
||||
}
|
||||
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, () -> banned));
|
||||
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned));
|
||||
} catch (Exception e) {
|
||||
errorLogger.error(e, ErrorContext.builder().related(event, event.getResult()).build());
|
||||
}
|
||||
@ -156,64 +129,28 @@ public class PlayerOnlineListener implements Listener {
|
||||
}
|
||||
|
||||
private void actOnJoinEvent(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
UUID playerUUID = event.getPlayer().getUniqueId();
|
||||
BukkitAFKListener.afkTracker.performedAction(playerUUID, time);
|
||||
|
||||
String world = player.getWorld().getName();
|
||||
String gm = player.getGameMode().name();
|
||||
|
||||
Database database = dbSystem.getDatabase();
|
||||
database.executeTransaction(new WorldNameStoreTransaction(serverUUID, world));
|
||||
|
||||
InetAddress address = player.getAddress().getAddress();
|
||||
Supplier<String> getHostName = () -> getHostname(player);
|
||||
|
||||
String playerName = player.getName();
|
||||
String displayName = player.getDisplayName();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerServerRegisterTransaction(playerUUID,
|
||||
player::getFirstPlayed, playerName, serverUUID, getHostName));
|
||||
database.executeTransaction(new OperatorStatusTransaction(playerUUID, serverUUID, player.isOp()));
|
||||
|
||||
ActiveSession session = new ActiveSession(playerUUID, serverUUID, time, world, gm);
|
||||
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
|
||||
session.getExtraData().put(ServerName.class, new ServerName(serverInfo.getServer().getIdentifiableName()));
|
||||
sessionCache.cacheSession(playerUUID, session)
|
||||
.ifPresent(previousSession -> database.executeTransaction(new SessionEndTransaction(previousSession)));
|
||||
|
||||
database.executeTransaction(new NicknameStoreTransaction(
|
||||
playerUUID, new Nickname(displayName, time, serverUUID),
|
||||
(uuid, name) -> nicknameCache.getDisplayName(playerUUID).map(name::equals).orElse(false)
|
||||
));
|
||||
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
|
||||
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
|
||||
}
|
||||
}
|
||||
|
||||
private String getHostname(Player player) {
|
||||
return joinAddresses.get(player.getUniqueId());
|
||||
playerJoinEventConsumer.onJoinGameServer(PlayerJoin.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BukkitPlayerData(event.getPlayer(), joinAddressCache.getNullableString(playerUUID)))
|
||||
.time(time)
|
||||
.build());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void beforePlayerQuit(PlayerQuitEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getName();
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
|
||||
try {
|
||||
playerLeaveEventConsumer.beforeLeave(PlayerLeave.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BukkitPlayerData(event.getPlayer(), null))
|
||||
.time(System.currentTimeMillis())
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
errorLogger.error(e, ErrorContext.builder().related(event).build());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR)
|
||||
@ -227,23 +164,13 @@ public class PlayerOnlineListener implements Listener {
|
||||
|
||||
private void actOnQuitEvent(PlayerQuitEvent event) {
|
||||
long time = System.currentTimeMillis();
|
||||
Player player = event.getPlayer();
|
||||
String playerName = player.getName();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
ServerUUID serverUUID = serverInfo.getServerUUID();
|
||||
|
||||
UUID playerUUID = event.getPlayer().getUniqueId();
|
||||
BukkitAFKListener.afkTracker.loggedOut(playerUUID, time);
|
||||
|
||||
joinAddresses.remove(playerUUID);
|
||||
nicknameCache.removeDisplayName(playerUUID);
|
||||
|
||||
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, player::isBanned));
|
||||
|
||||
sessionCache.endSession(playerUUID, time)
|
||||
.ifPresent(endedSession -> dbSystem.getDatabase().executeTransaction(new SessionEndTransaction(endedSession)));
|
||||
|
||||
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
|
||||
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
|
||||
}
|
||||
playerLeaveEventConsumer.onLeaveGameServer(PlayerLeave.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BukkitPlayerData(event.getPlayer(), null))
|
||||
.time(System.currentTimeMillis())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.settings.config.WorldAliasSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.WorldNameStoreTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.StoreWorldNameTransaction;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import org.bukkit.entity.Player;
|
||||
@ -72,7 +72,7 @@ public class WorldChangeListener implements Listener {
|
||||
String worldName = player.getWorld().getName();
|
||||
String gameMode = player.getGameMode().name();
|
||||
|
||||
dbSystem.getDatabase().executeTransaction(new WorldNameStoreTransaction(serverInfo.getServerUUID(), worldName));
|
||||
dbSystem.getDatabase().executeTransaction(new StoreWorldNameTransaction(serverInfo.getServerUUID(), worldName));
|
||||
worldAliasSettings.addWorld(worldName);
|
||||
|
||||
Optional<ActiveSession> cachedSession = SessionCache.getCachedSession(uuid);
|
||||
|
@ -44,6 +44,7 @@ import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
@ -62,16 +63,16 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
//the server is pinging the client every 40 Ticks (2 sec) - so check it then
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/PlayerConnection.java#L178
|
||||
|
||||
private static boolean pingMethodAvailable;
|
||||
|
||||
private final Map<UUID, Long> startRecording;
|
||||
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
|
||||
|
||||
private final Listeners listeners;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final RunnableFactory runnableFactory;
|
||||
|
||||
private final boolean pingMethodAvailable;
|
||||
private PingMethod pingMethod;
|
||||
|
||||
@Inject
|
||||
@ -79,14 +80,13 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
Listeners listeners,
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
ServerInfo serverInfo,
|
||||
RunnableFactory runnableFactory
|
||||
ServerInfo serverInfo
|
||||
) {
|
||||
this.listeners = listeners;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.serverInfo = serverInfo;
|
||||
this.runnableFactory = runnableFactory;
|
||||
startRecording = new ConcurrentHashMap<>();
|
||||
playerHistory = new HashMap<>();
|
||||
|
||||
Optional<PingMethod> pingMethod = loadPingMethod();
|
||||
@ -117,7 +117,7 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
}
|
||||
Logger.getGlobal().log(
|
||||
Level.WARNING,
|
||||
"Plan: No Ping method found - Ping will not be recorded:" + reasonsForUnavailability.toString()
|
||||
() -> "Plan: No Ping method found - Ping will not be recorded:" + reasonsForUnavailability.toString()
|
||||
);
|
||||
|
||||
return Optional.empty();
|
||||
@ -138,6 +138,16 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
|
||||
Iterator<Map.Entry<UUID, Long>> starts = startRecording.entrySet().iterator();
|
||||
while (starts.hasNext()) {
|
||||
Map.Entry<UUID, Long> start = starts.next();
|
||||
if (time >= start.getValue()) {
|
||||
addPlayer(start.getKey());
|
||||
starts.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
@ -164,11 +174,12 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void addPlayer(Player player) {
|
||||
playerHistory.put(player.getUniqueId(), new ArrayList<>());
|
||||
public void addPlayer(UUID uuid) {
|
||||
playerHistory.put(uuid, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void removePlayer(Player player) {
|
||||
startRecording.remove(player.getUniqueId());
|
||||
playerHistory.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
@ -182,15 +193,11 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
|
||||
if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) {
|
||||
Long pingDelayMs = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
|
||||
if (pingDelayMs >= TimeUnit.HOURS.toMillis(2L)) {
|
||||
return;
|
||||
}
|
||||
runnableFactory.create(() -> {
|
||||
if (player.isOnline()) {
|
||||
addPlayer(player);
|
||||
}
|
||||
}).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS));
|
||||
startRecording.put(player.getUniqueId(), System.currentTimeMillis() + pingDelayMs);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016-2018
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.timed;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class PingMethodReflection {
|
||||
|
||||
private PingMethodReflection() {
|
||||
/* Static utility class */
|
||||
}
|
||||
|
||||
/**
|
||||
* Get methods for getting the ping value.
|
||||
*
|
||||
* @param craftPlayerClass Class for player
|
||||
* @param entityPlayer Class for player entity
|
||||
* @param methodName getHandle method
|
||||
* @param fieldName Latency field name
|
||||
* @return [getHandle method, getPlayer method]
|
||||
* @throws NoSuchMethodException Method doesn't exist
|
||||
* @throws IllegalAccessException Method can't be accessed
|
||||
* @throws NoSuchFieldException Field can't be accessed
|
||||
* @throws IllegalArgumentException Something else
|
||||
*/
|
||||
public static MethodHandle[] getMethods(
|
||||
Class<?> craftPlayerClass,
|
||||
Class<?> entityPlayer,
|
||||
String methodName,
|
||||
String fieldName
|
||||
) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
Method handleMethod = craftPlayerClass.getDeclaredMethod(methodName);
|
||||
|
||||
return new MethodHandle[]{
|
||||
lookup.unreflect(handleMethod),
|
||||
lookup.findGetter(entityPlayer, fieldName, Integer.TYPE)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectiveLatencyFieldMethod implements PingMethod {
|
||||
|
||||
@ -37,26 +35,26 @@ public class ReflectiveLatencyFieldMethod implements PingMethod {
|
||||
|
||||
private String reasonForUnavailability;
|
||||
|
||||
private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
|
||||
MethodHandle[] methodHandles = PingMethodReflection.getMethods(
|
||||
Reflection.getCraftBukkitClass("entity.CraftPlayer"),
|
||||
Reflection.getMinecraftClass("EntityPlayer"),
|
||||
"getHandle",
|
||||
"latency"
|
||||
);
|
||||
getHandleMethod = methodHandles[0];
|
||||
pingField = methodHandles[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MethodHandle localHandle = null;
|
||||
MethodHandle localPing = null;
|
||||
try {
|
||||
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
|
||||
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
|
||||
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
|
||||
localHandle = lookup.unreflect(getHandleMethod);
|
||||
|
||||
localPing = lookup.findGetter(entityPlayer, "latency", Integer.TYPE);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) {
|
||||
setMethods();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException |
|
||||
IllegalArgumentException reflectiveEx) {
|
||||
reasonForUnavailability = reflectiveEx.toString();
|
||||
return false;
|
||||
}
|
||||
getHandleMethod = localHandle;
|
||||
pingField = localPing;
|
||||
return pingField != null;
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectiveLevelEntityPlayerLatencyFieldMethod implements PingMethod {
|
||||
|
||||
@ -37,26 +35,34 @@ public class ReflectiveLevelEntityPlayerLatencyFieldMethod implements PingMethod
|
||||
|
||||
private String reasonForUnavailability;
|
||||
|
||||
private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
|
||||
MethodHandle[] methodHandles = PingMethodReflection.getMethods(
|
||||
Reflection.getCraftBukkitClass("entity.CraftPlayer"),
|
||||
getEntityPlayer(),
|
||||
"getHandle",
|
||||
"latency"
|
||||
);
|
||||
getHandleMethod = methodHandles[0];
|
||||
pingField = methodHandles[1];
|
||||
}
|
||||
|
||||
private static Class<?> getEntityPlayer() throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
} catch (NullPointerException classLoaderError) {
|
||||
throw new ClassNotFoundException("net.minecraft.server.level.EntityPlayer");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MethodHandle localHandle = null;
|
||||
MethodHandle localPing = null;
|
||||
try {
|
||||
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
|
||||
Class<?> entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
|
||||
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
|
||||
localHandle = lookup.unreflect(getHandleMethod);
|
||||
|
||||
localPing = lookup.findGetter(entityPlayer, "latency", Integer.TYPE);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) {
|
||||
setMethods();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException |
|
||||
IllegalArgumentException reflectiveEx) {
|
||||
reasonForUnavailability = reflectiveEx.toString();
|
||||
return false;
|
||||
}
|
||||
getHandleMethod = localHandle;
|
||||
pingField = localPing;
|
||||
return pingField != null;
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectivePingFieldMethod implements PingMethod {
|
||||
|
||||
@ -37,26 +35,26 @@ public class ReflectivePingFieldMethod implements PingMethod {
|
||||
|
||||
private String reasonForUnavailability;
|
||||
|
||||
private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException {
|
||||
MethodHandle[] methodHandles = PingMethodReflection.getMethods(
|
||||
Reflection.getCraftBukkitClass("entity.CraftPlayer"),
|
||||
Reflection.getMinecraftClass("EntityPlayer"),
|
||||
"getHandle",
|
||||
"ping"
|
||||
);
|
||||
getHandleMethod = methodHandles[0];
|
||||
pingField = methodHandles[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MethodHandle localHandle = null;
|
||||
MethodHandle localPing = null;
|
||||
try {
|
||||
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
|
||||
Class<?> entityPlayer = Reflection.getMinecraftClass("EntityPlayer");
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
|
||||
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
|
||||
localHandle = lookup.unreflect(getHandleMethod);
|
||||
|
||||
localPing = lookup.findGetter(entityPlayer, "ping", Integer.TYPE);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | IllegalArgumentException reflectiveEx) {
|
||||
setMethods();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException |
|
||||
IllegalArgumentException reflectiveEx) {
|
||||
reasonForUnavailability = reflectiveEx.toString();
|
||||
return false;
|
||||
}
|
||||
getHandleMethod = localHandle;
|
||||
pingField = localPing;
|
||||
return pingField != null;
|
||||
}
|
||||
|
||||
|
@ -27,8 +27,6 @@ import com.djrapitops.plan.utilities.java.Reflection;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectiveUnmappedLatencyFieldMethod implements PingMethod {
|
||||
|
||||
@ -37,27 +35,34 @@ public class ReflectiveUnmappedLatencyFieldMethod implements PingMethod {
|
||||
|
||||
private String reasonForUnavailability;
|
||||
|
||||
private static void setMethods() throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException {
|
||||
MethodHandle[] methodHandles = PingMethodReflection.getMethods(
|
||||
Reflection.getCraftBukkitClass("entity.CraftPlayer"),
|
||||
getEntityPlayer(),
|
||||
"getHandle",
|
||||
"e"
|
||||
);
|
||||
getHandleMethod = methodHandles[0];
|
||||
pingField = methodHandles[1];
|
||||
}
|
||||
|
||||
private static Class<?> getEntityPlayer() throws ClassNotFoundException {
|
||||
try {
|
||||
return Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
} catch (NullPointerException classLoaderError) {
|
||||
throw new ClassNotFoundException("net.minecraft.server.level.EntityPlayer");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
MethodHandle localHandle = null;
|
||||
MethodHandle localPing = null;
|
||||
try {
|
||||
Class<?> craftPlayerClass = Reflection.getCraftBukkitClass("entity.CraftPlayer");
|
||||
Class<?> entityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
|
||||
|
||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
|
||||
Method getHandleMethod = craftPlayerClass.getDeclaredMethod("getHandle");
|
||||
localHandle = lookup.unreflect(getHandleMethod);
|
||||
|
||||
// e is latency in unmapped jar
|
||||
localPing = lookup.findGetter(entityPlayer, "e", Integer.TYPE);
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException | IllegalArgumentException reflectiveEx) {
|
||||
setMethods();
|
||||
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException |
|
||||
IllegalArgumentException reflectiveEx) {
|
||||
reasonForUnavailability = reflectiveEx.toString();
|
||||
return false;
|
||||
}
|
||||
getHandleMethod = localHandle;
|
||||
pingField = localPing;
|
||||
return pingField != null;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,10 @@
|
||||
package com.djrapitops.plan.modules.bukkit;
|
||||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.addons.placeholderapi.PlaceholderCacheRefreshTask;
|
||||
import com.djrapitops.plan.delivery.web.ResourceWriteTask;
|
||||
import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.ShutdownDataPreservation;
|
||||
@ -26,6 +30,7 @@ import com.djrapitops.plan.gathering.timed.ServerTPSCounter;
|
||||
import com.djrapitops.plan.gathering.timed.SystemUsageBuffer;
|
||||
import com.djrapitops.plan.settings.upkeep.ConfigStoreTask;
|
||||
import com.djrapitops.plan.storage.upkeep.DBCleanTask;
|
||||
import com.djrapitops.plan.storage.upkeep.ExtensionDisableOnGameServerTask;
|
||||
import com.djrapitops.plan.storage.upkeep.LogsFolderCleanTask;
|
||||
import com.djrapitops.plan.storage.upkeep.OldDependencyCacheDeletionTask;
|
||||
import dagger.Binds;
|
||||
@ -83,4 +88,24 @@ public interface BukkitTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindOldDependencyCacheDeletion(OldDependencyCacheDeletionTask deletionTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindExtensionDisableOnGameServerTask(ExtensionDisableOnGameServerTask extensionDisableOnGameServerTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindPlaceholderWarmupTask(PlaceholderCacheRefreshTask placeholderCacheRefreshTask);
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
public class BukkitDBSystem extends DBSystem {
|
||||
|
||||
private final PlanConfig config;
|
||||
|
||||
@Inject
|
||||
public BukkitDBSystem(
|
||||
@ -42,8 +41,7 @@ public class BukkitDBSystem extends DBSystem {
|
||||
PlanConfig config,
|
||||
PluginLogger logger
|
||||
) {
|
||||
super(locale, sqLiteDB, logger);
|
||||
this.config = config;
|
||||
super(config, locale, sqLiteDB, logger);
|
||||
|
||||
databases.add(mySQLDB);
|
||||
databases.add(sqLiteDB.usingDefaultFile());
|
||||
|
@ -71,7 +71,7 @@ public final class Reflection {
|
||||
field.setAccessible(true);
|
||||
|
||||
// A function for retrieving a specific field value
|
||||
return new FieldAccessor<T>() {
|
||||
return new FieldAccessor<>() {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -140,7 +140,7 @@ public final class Reflection {
|
||||
private static Class<?> getCanonicalClass(String canonicalName) {
|
||||
try {
|
||||
return Class.forName(canonicalName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (ClassNotFoundException | NullPointerException e) {
|
||||
throw new IllegalArgumentException("Cannot find " + canonicalName, e);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,8 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.listeners;
|
||||
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.gathering.listeners.bukkit.BukkitAFKListener;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.TimeSettings;
|
||||
@ -51,11 +53,15 @@ class BukkitAFKListenerTest {
|
||||
when(config.get(TimeSettings.AFK_THRESHOLD)).thenReturn(TimeUnit.MINUTES.toMillis(3));
|
||||
errorLogger = Mockito.mock(ErrorLogger.class);
|
||||
underTest = new BukkitAFKListener(config, errorLogger);
|
||||
|
||||
new SessionCache().cacheSession(TestConstants.PLAYER_ONE_UUID, new ActiveSession(null, null, 0, null, null));
|
||||
new SessionCache().cacheSession(TestConstants.PLAYER_TWO_UUID, new ActiveSession(null, null, 0, null, null));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void ensureNoErrors() {
|
||||
verifyNoInteractions(errorLogger);
|
||||
SessionCache.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.DaggerPlanBukkitComponent;
|
||||
import com.djrapitops.plan.PlanBukkitComponent;
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.storage.database.SQLDB;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.command.ConsoleCommandSender;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
@ -44,6 +45,7 @@ public class BukkitMockComponent {
|
||||
|
||||
public BukkitMockComponent(Path tempDir) {
|
||||
this.tempDir = tempDir;
|
||||
SQLDB.setDownloadDriver(false);
|
||||
}
|
||||
|
||||
public PlanPlugin getPlanMock() throws Exception {
|
||||
|
@ -1,20 +1,20 @@
|
||||
dependencies {
|
||||
compileOnly project(":common")
|
||||
implementation project(path: ":common", configuration: 'shadow')
|
||||
compileOnly project(":api")
|
||||
implementation project(":api")
|
||||
implementation project(":common")
|
||||
|
||||
implementation "net.playeranalytics:platform-abstraction-layer-bungeecord:$palVersion"
|
||||
implementation "org.bstats:bstats-bungeecord:$bstatsVersion"
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-bungeecord:$palVersion"
|
||||
shadow "org.bstats:bstats-bungeecord:$bstatsVersion"
|
||||
|
||||
compileOnly "net.md-5:bungeecord-api:$bungeeVersion"
|
||||
compileOnly "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion"
|
||||
|
||||
testImplementation "net.md-5:bungeecord-api:$bungeeVersion"
|
||||
testImplementation "com.imaginarycode.minecraft:RedisBungee:$redisBungeeVersion"
|
||||
|
||||
testImplementation project(path: ":common", configuration: 'testArtifacts')
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
relocate 'org.bstats', 'com.djrapitops.plan.utilities.metrics'
|
||||
relocate 'org.slf4j', 'plan.org.slf4j'
|
||||
configurations = [project.configurations.shadow]
|
||||
relocate 'org.bstats', 'net.playeranalytics.bstats.utilities.metrics'
|
||||
}
|
@ -23,6 +23,8 @@ import com.djrapitops.plan.exceptions.EnableException;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||
import com.djrapitops.plan.settings.theme.PlanColorScheme;
|
||||
import com.djrapitops.plan.utilities.java.ThreadContextClassLoaderSwap;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.playeranalytics.plugin.BungeePlatformLayer;
|
||||
import net.playeranalytics.plugin.PlatformAbstractionLayer;
|
||||
@ -46,6 +48,7 @@ public class PlanBungee extends Plugin implements PlanPlugin {
|
||||
private PluginLogger logger;
|
||||
private RunnableFactory runnableFactory;
|
||||
private PlatformAbstractionLayer abstractionLayer;
|
||||
private ErrorLogger errorLogger;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
@ -61,7 +64,8 @@ public class PlanBungee extends Plugin implements PlanPlugin {
|
||||
.abstractionLayer(abstractionLayer)
|
||||
.build();
|
||||
try {
|
||||
system = component.system();
|
||||
system = ThreadContextClassLoaderSwap.performOperation(getClass().getClassLoader(), component::system);
|
||||
errorLogger = component.errorLogger();
|
||||
locale = system.getLocaleSystem().getLocale();
|
||||
system.enable();
|
||||
|
||||
@ -107,7 +111,7 @@ public class PlanBungee extends Plugin implements PlanPlugin {
|
||||
return;
|
||||
}
|
||||
for (String name : command.getAliases()) {
|
||||
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(runnableFactory, system.getErrorLogger(), command, name));
|
||||
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(runnableFactory, errorLogger, command, name));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ package com.djrapitops.plan;
|
||||
import com.djrapitops.plan.commands.PlanCommand;
|
||||
import com.djrapitops.plan.modules.*;
|
||||
import com.djrapitops.plan.modules.bungee.*;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Component;
|
||||
import net.playeranalytics.plugin.PlatformAbstractionLayer;
|
||||
@ -50,6 +51,8 @@ public interface PlanBungeeComponent {
|
||||
|
||||
PlanSystem system();
|
||||
|
||||
ErrorLogger errorLogger();
|
||||
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
|
||||
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.commands.use.MessageBuilder;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||
import net.md_5.bungee.api.chat.HoverEvent;
|
||||
import net.md_5.bungee.api.chat.hover.content.Text;
|
||||
import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.util.Arrays;
|
||||
@ -68,7 +69,7 @@ class BungeePartBuilder implements MessageBuilder {
|
||||
|
||||
@Override
|
||||
public MessageBuilder hover(String text) {
|
||||
part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(text).create()));
|
||||
part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(text)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ class BungeePartBuilder implements MessageBuilder {
|
||||
public MessageBuilder hover(Collection<String> lines) {
|
||||
ComponentBuilder hoverMsg = new ComponentBuilder("");
|
||||
hoverMsg.append(new TextStringBuilder().appendWithSeparators(lines, "\n").build());
|
||||
part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverMsg.create()));
|
||||
part.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(hoverMsg.create())));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.domain;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BungeePlayerData implements PlatformPlayerData {
|
||||
|
||||
private final ProxiedPlayer player;
|
||||
|
||||
public BungeePlayerData(ProxiedPlayer player) {this.player = player;}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return player.getUniqueId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return player.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<InetAddress> getIPAddress() {
|
||||
Optional<InetAddress> ip = getIPFromSocketAddress();
|
||||
if (ip.isPresent()) return ip;
|
||||
return getIpFromOldMethod();
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // ProxiedPlayer#getAddress is deprecated
|
||||
private Optional<InetAddress> getIpFromOldMethod() {
|
||||
try {
|
||||
return Optional.ofNullable(player.getAddress()).map(InetSocketAddress::getAddress);
|
||||
} catch (NoSuchMethodError e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<InetAddress> getIPFromSocketAddress() {
|
||||
try {
|
||||
SocketAddress socketAddress = player.getSocketAddress();
|
||||
if (socketAddress instanceof InetSocketAddress) {
|
||||
return Optional.of(((InetSocketAddress) socketAddress).getAddress());
|
||||
}
|
||||
|
||||
// Unix domain socket address requires Java 16 compatibility.
|
||||
// These connections come from the same physical machine
|
||||
Class<?> jdk16SocketAddressType = Class.forName("java.net.UnixDomainSocketAddress");
|
||||
if (jdk16SocketAddressType.isAssignableFrom(socketAddress.getClass())) {
|
||||
return Optional.of(InetAddress.getLocalHost());
|
||||
}
|
||||
} catch (NoSuchMethodError | ClassNotFoundException | UnknownHostException e) {
|
||||
// Ignored
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -16,23 +16,13 @@
|
||||
*/
|
||||
package com.djrapitops.plan.gathering.listeners.bungee;
|
||||
|
||||
import com.djrapitops.plan.delivery.domain.PlayerName;
|
||||
import com.djrapitops.plan.delivery.domain.ServerName;
|
||||
import com.djrapitops.plan.delivery.export.Exporter;
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
import com.djrapitops.plan.extension.ExtensionSvc;
|
||||
import com.djrapitops.plan.gathering.cache.SessionCache;
|
||||
import com.djrapitops.plan.gathering.domain.ActiveSession;
|
||||
import com.djrapitops.plan.gathering.geolocation.GeolocationCache;
|
||||
import com.djrapitops.plan.gathering.domain.BungeePlayerData;
|
||||
import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
|
||||
import com.djrapitops.plan.gathering.domain.event.PlayerLeave;
|
||||
import com.djrapitops.plan.gathering.events.PlayerJoinEventConsumer;
|
||||
import com.djrapitops.plan.gathering.events.PlayerLeaveEventConsumer;
|
||||
import com.djrapitops.plan.gathering.events.PlayerSwitchServerEventConsumer;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.ExportSettings;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.GeoInfoStoreTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.events.PlayerRegisterTransaction;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@ -44,8 +34,6 @@ import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.net.InetAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Player Join listener for Bungee.
|
||||
@ -54,34 +42,24 @@ import java.util.UUID;
|
||||
*/
|
||||
public class PlayerOnlineListener implements Listener {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final Processing processing;
|
||||
private final DBSystem dbSystem;
|
||||
private final ExtensionSvc extensionService;
|
||||
private final Exporter exporter;
|
||||
private final GeolocationCache geolocationCache;
|
||||
private final SessionCache sessionCache;
|
||||
private final PlayerJoinEventConsumer joinEventConsumer;
|
||||
private final PlayerLeaveEventConsumer leaveEventConsumer;
|
||||
private final PlayerSwitchServerEventConsumer switchServerEventConsumer;
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final ErrorLogger errorLogger;
|
||||
|
||||
@Inject
|
||||
public PlayerOnlineListener(
|
||||
PlanConfig config,
|
||||
Processing processing,
|
||||
DBSystem dbSystem,
|
||||
ExtensionSvc extensionService,
|
||||
Exporter exporter, GeolocationCache geolocationCache,
|
||||
SessionCache sessionCache,
|
||||
PlayerJoinEventConsumer joinEventConsumer,
|
||||
PlayerLeaveEventConsumer leaveEventConsumer,
|
||||
PlayerSwitchServerEventConsumer switchServerEventConsumer,
|
||||
ServerInfo serverInfo,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.config = config;
|
||||
this.processing = processing;
|
||||
this.dbSystem = dbSystem;
|
||||
this.extensionService = extensionService;
|
||||
this.exporter = exporter;
|
||||
this.geolocationCache = geolocationCache;
|
||||
this.sessionCache = sessionCache;
|
||||
this.joinEventConsumer = joinEventConsumer;
|
||||
this.leaveEventConsumer = leaveEventConsumer;
|
||||
this.switchServerEventConsumer = switchServerEventConsumer;
|
||||
this.serverInfo = serverInfo;
|
||||
this.errorLogger = errorLogger;
|
||||
}
|
||||
@ -96,82 +74,48 @@ public class PlayerOnlineListener implements Listener {
|
||||
}
|
||||
|
||||
private void actOnLogin(PostLoginEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getName();
|
||||
InetAddress address = player.getAddress().getAddress();
|
||||
long time = System.currentTimeMillis();
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
|
||||
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
|
||||
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
boolean gatheringGeolocations = config.isTrue(DataGatheringSettings.GEOLOCATIONS);
|
||||
if (gatheringGeolocations) {
|
||||
database.executeTransaction(
|
||||
new GeoInfoStoreTransaction(playerUUID, address, time, geolocationCache::getCountry)
|
||||
);
|
||||
}
|
||||
|
||||
database.executeTransaction(new PlayerRegisterTransaction(playerUUID, () -> time, playerName));
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_JOIN));
|
||||
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
|
||||
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
|
||||
}
|
||||
joinEventConsumer.onJoinProxyServer(PlayerJoin.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BungeePlayerData(player))
|
||||
.time(time)
|
||||
.build());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void beforeLogout(PlayerDisconnectEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
String playerName = player.getName();
|
||||
processing.submitNonCritical(() -> extensionService.updatePlayerValues(playerUUID, playerName, CallEvents.PLAYER_LEAVE));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onLogout(PlayerDisconnectEvent event) {
|
||||
try {
|
||||
actOnLogout(event);
|
||||
leaveEventConsumer.beforeLeave(PlayerLeave.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BungeePlayerData(event.getPlayer()))
|
||||
.time(System.currentTimeMillis())
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
errorLogger.error(e, ErrorContext.builder().related(event).build());
|
||||
}
|
||||
}
|
||||
|
||||
private void actOnLogout(PlayerDisconnectEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
String playerName = player.getName();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
sessionCache.endSession(playerUUID, System.currentTimeMillis());
|
||||
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
|
||||
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onLogout(PlayerDisconnectEvent event) {
|
||||
try {
|
||||
leaveEventConsumer.onLeaveProxyServer(PlayerLeave.builder()
|
||||
.server(serverInfo.getServer())
|
||||
.player(new BungeePlayerData(event.getPlayer()))
|
||||
.time(System.currentTimeMillis())
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
errorLogger.error(e, ErrorContext.builder().related(event).build());
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onServerSwitch(ServerSwitchEvent event) {
|
||||
try {
|
||||
actOnServerSwitch(event);
|
||||
switchServerEventConsumer.onServerSwitch(new BungeePlayerData(event.getPlayer()), System.currentTimeMillis());
|
||||
} catch (Exception e) {
|
||||
errorLogger.error(e, ErrorContext.builder().related(event).build());
|
||||
}
|
||||
}
|
||||
|
||||
private void actOnServerSwitch(ServerSwitchEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
String playerName = player.getName();
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
// Replaces the current session in the cache.
|
||||
ActiveSession session = new ActiveSession(playerUUID, serverInfo.getServerUUID(), time, null, null);
|
||||
session.getExtraData().put(PlayerName.class, new PlayerName(playerName));
|
||||
session.getExtraData().put(ServerName.class, new ServerName("Proxy Server"));
|
||||
sessionCache.cacheSession(playerUUID, session);
|
||||
if (config.isTrue(ExportSettings.EXPORT_ON_ONLINE_STATUS_CHANGE)) {
|
||||
processing.submitNonCritical(() -> exporter.exportPlayerPage(playerUUID, playerName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import net.playeranalytics.plugin.server.Listeners;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@ -54,35 +55,43 @@ import java.util.concurrent.TimeUnit;
|
||||
@Singleton
|
||||
public class BungeePingCounter extends TaskSystem.Task implements Listener {
|
||||
|
||||
private final Map<UUID, Long> startRecording;
|
||||
private final Map<UUID, List<DateObj<Integer>>> playerHistory;
|
||||
|
||||
private final Listeners listeners;
|
||||
private final PlanConfig config;
|
||||
private final DBSystem dbSystem;
|
||||
private final ServerInfo serverInfo;
|
||||
private final RunnableFactory runnableFactory;
|
||||
|
||||
@Inject
|
||||
public BungeePingCounter(
|
||||
Listeners listeners,
|
||||
PlanConfig config,
|
||||
DBSystem dbSystem,
|
||||
ServerInfo serverInfo,
|
||||
RunnableFactory runnableFactory
|
||||
ServerInfo serverInfo
|
||||
) {
|
||||
this.listeners = listeners;
|
||||
this.config = config;
|
||||
this.dbSystem = dbSystem;
|
||||
this.serverInfo = serverInfo;
|
||||
this.runnableFactory = runnableFactory;
|
||||
startRecording = new ConcurrentHashMap<>();
|
||||
playerHistory = new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long time = System.currentTimeMillis();
|
||||
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
|
||||
|
||||
Iterator<Map.Entry<UUID, Long>> starts = startRecording.entrySet().iterator();
|
||||
while (starts.hasNext()) {
|
||||
Map.Entry<UUID, Long> start = starts.next();
|
||||
if (time >= start.getValue()) {
|
||||
addPlayer(start.getKey());
|
||||
starts.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<UUID, List<DateObj<Integer>>>> iterator = playerHistory.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<UUID, List<DateObj<Integer>>> entry = iterator.next();
|
||||
UUID uuid = entry.getKey();
|
||||
@ -119,12 +128,13 @@ public class BungeePingCounter extends TaskSystem.Task implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void addPlayer(ProxiedPlayer player) {
|
||||
playerHistory.put(player.getUniqueId(), new ArrayList<>());
|
||||
public void addPlayer(UUID uuid) {
|
||||
playerHistory.put(uuid, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void removePlayer(ProxiedPlayer player) {
|
||||
playerHistory.remove(player.getUniqueId());
|
||||
startRecording.remove(player.getUniqueId());
|
||||
}
|
||||
|
||||
private int getPing(ProxiedPlayer player) {
|
||||
@ -134,15 +144,11 @@ public class BungeePingCounter extends TaskSystem.Task implements Listener {
|
||||
@EventHandler
|
||||
public void onPlayerJoin(ServerConnectedEvent joinEvent) {
|
||||
ProxiedPlayer player = joinEvent.getPlayer();
|
||||
Long pingDelay = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
|
||||
if (pingDelay >= TimeUnit.HOURS.toMillis(2L)) {
|
||||
Long pingDelayMs = config.get(TimeSettings.PING_PLAYER_LOGIN_DELAY);
|
||||
if (pingDelayMs >= TimeUnit.HOURS.toMillis(2L)) {
|
||||
return;
|
||||
}
|
||||
runnableFactory.create(() -> {
|
||||
if (player.isConnected()) {
|
||||
addPlayer(player);
|
||||
}
|
||||
}).runTaskLater(TimeAmount.toTicks(pingDelay, TimeUnit.MILLISECONDS));
|
||||
startRecording.put(player.getUniqueId(), System.currentTimeMillis() + pingDelayMs);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
|
@ -28,6 +28,7 @@ import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
@ -38,6 +39,7 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
public class BungeeServerInfo extends ServerInfo {
|
||||
|
||||
private final String currentVersion;
|
||||
private final ServerLoader fromFile;
|
||||
private final ServerLoader fromDatabase;
|
||||
|
||||
@ -49,6 +51,7 @@ public class BungeeServerInfo extends ServerInfo {
|
||||
|
||||
@Inject
|
||||
public BungeeServerInfo(
|
||||
@Named("currentVersion") String currentVersion,
|
||||
ServerProperties serverProperties,
|
||||
ServerFileLoader fromFile,
|
||||
ServerDBLoader fromDatabase,
|
||||
@ -58,6 +61,7 @@ public class BungeeServerInfo extends ServerInfo {
|
||||
PluginLogger logger
|
||||
) {
|
||||
super(serverProperties);
|
||||
this.currentVersion = currentVersion;
|
||||
this.fromFile = fromFile;
|
||||
this.fromDatabase = fromDatabase;
|
||||
this.processing = processing;
|
||||
@ -89,7 +93,7 @@ public class BungeeServerInfo extends ServerInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws EnableException
|
||||
* @throws EnableException If IP setting is unset
|
||||
*/
|
||||
private void checkIfDefaultIP() {
|
||||
String ip = serverProperties.getIp();
|
||||
@ -101,7 +105,7 @@ public class BungeeServerInfo extends ServerInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws EnableException
|
||||
* @throws EnableException If IP setting is unset
|
||||
*/
|
||||
private Server registerServer() {
|
||||
Server proxy = createServerObject();
|
||||
@ -115,11 +119,11 @@ public class BungeeServerInfo extends ServerInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws EnableException
|
||||
* @throws EnableException If IP setting is unset
|
||||
*/
|
||||
private Server createServerObject() {
|
||||
ServerUUID serverUUID = generateNewUUID();
|
||||
String accessAddress = addresses.getAccessAddress().orElseThrow(() -> new EnableException("Velocity can not have '0.0.0.0' or '' as an address. Set up 'Server.IP' setting."));
|
||||
return new Server(-1, serverUUID, "BungeeCord", accessAddress, true);
|
||||
return new Server(-1, serverUUID, "BungeeCord", accessAddress, true, currentVersion);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,9 @@
|
||||
package com.djrapitops.plan.modules.bungee;
|
||||
|
||||
import com.djrapitops.plan.TaskSystem;
|
||||
import com.djrapitops.plan.delivery.web.ResourceWriteTask;
|
||||
import com.djrapitops.plan.delivery.web.WebAssetVersionCheckTask;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.ActiveCookieExpiryCleanupTask;
|
||||
import com.djrapitops.plan.delivery.webserver.cache.JSONFileStorage;
|
||||
import com.djrapitops.plan.extension.ExtensionServerDataUpdater;
|
||||
import com.djrapitops.plan.gathering.timed.BungeePingCounter;
|
||||
@ -72,4 +75,16 @@ public interface BungeeTaskModule {
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindOldDependencyCacheDeletion(OldDependencyCacheDeletionTask deletionTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindResourceWriteTask(ResourceWriteTask resourceWriteTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindWebAssetVersionCheckTask(WebAssetVersionCheckTask webAssetVersionCheckTask);
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
TaskSystem.Task bindActiveCookieStoreExpiryTask(ActiveCookieExpiryCleanupTask activeCookieExpiryCleanupTask);
|
||||
}
|
||||
|
@ -48,13 +48,13 @@ class BungeeSystemTest {
|
||||
private DBPreparer dbPreparer;
|
||||
|
||||
@BeforeEach
|
||||
void prepareSystem(@TempDir Path temp) throws Exception {
|
||||
void prepareSystem(@TempDir Path temp) {
|
||||
component = new BungeeMockComponent(temp);
|
||||
dbPreparer = new DBPreparer(component.getPlanSystem(), TEST_PORT_NUMBER);
|
||||
dbPreparer = new DBPreparer(new BungeeSystemTestDependencies(component.getPlanSystem()), TEST_PORT_NUMBER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bungeeEnables() throws Exception {
|
||||
void bungeeEnables() {
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
@ -75,45 +75,41 @@ class BungeeSystemTest {
|
||||
|
||||
@Test
|
||||
void bungeeDoesNotEnableWithDefaultIP() {
|
||||
EnableException thrown = assertThrows(EnableException.class, () -> {
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
|
||||
config.set(ProxySettings.IP, "0.0.0.0");
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
|
||||
config.set(ProxySettings.IP, "0.0.0.0");
|
||||
|
||||
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
|
||||
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
|
||||
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
|
||||
dbSystem.setActiveDatabase(db);
|
||||
DBSystem dbSystem = bungeeSystem.getDatabaseSystem();
|
||||
SQLiteDB db = dbSystem.getSqLiteFactory().usingDefaultFile();
|
||||
db.setTransactionExecutorServiceProvider(MoreExecutors::newDirectExecutorService);
|
||||
dbSystem.setActiveDatabase(db);
|
||||
|
||||
bungeeSystem.enable(); // Throws EnableException
|
||||
} finally {
|
||||
bungeeSystem.disable();
|
||||
}
|
||||
});
|
||||
EnableException thrown = assertThrows(EnableException.class, bungeeSystem::enable);
|
||||
assertEquals("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.", thrown.getMessage());
|
||||
} finally {
|
||||
bungeeSystem.disable();
|
||||
}
|
||||
|
||||
assertEquals("IP setting still 0.0.0.0 - Configure Alternative_IP/IP that connects to the Proxy server.", thrown.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnableNoMySQL() {
|
||||
assertThrows(EnableException.class, () -> {
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
|
||||
config.set(ProxySettings.IP, "8.8.8.8");
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
config.set(WebserverSettings.PORT, TEST_PORT_NUMBER);
|
||||
config.set(ProxySettings.IP, "8.8.8.8");
|
||||
|
||||
bungeeSystem.enable(); // Throws EnableException
|
||||
} finally {
|
||||
bungeeSystem.disable();
|
||||
}
|
||||
});
|
||||
assertThrows(EnableException.class, bungeeSystem::enable);
|
||||
} finally {
|
||||
bungeeSystem.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEnableWithMySQL() throws Exception {
|
||||
void testEnableWithMySQL() {
|
||||
PlanSystem bungeeSystem = component.getPlanSystem();
|
||||
try {
|
||||
PlanConfig config = bungeeSystem.getConfigSystem().getConfig();
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import utilities.DBPreparer;
|
||||
|
||||
public class BungeeSystemTestDependencies implements DBPreparer.Dependencies {
|
||||
|
||||
private final PlanSystem system;
|
||||
|
||||
public BungeeSystemTestDependencies(PlanSystem system) {
|
||||
this.system = system;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlanConfig config() {
|
||||
return system.getConfigSystem().getConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBSystem dbSystem() {
|
||||
return system.getDatabaseSystem();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.DaggerPlanBungeeComponent;
|
||||
import com.djrapitops.plan.PlanBungee;
|
||||
import com.djrapitops.plan.PlanBungeeComponent;
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.storage.database.SQLDB;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@ -37,9 +38,10 @@ public class BungeeMockComponent {
|
||||
|
||||
public BungeeMockComponent(Path tempDir) {
|
||||
this.tempDir = tempDir;
|
||||
SQLDB.setDownloadDriver(false);
|
||||
}
|
||||
|
||||
public PlanBungee getPlanMock() throws Exception {
|
||||
public PlanBungee getPlanMock() {
|
||||
if (planMock == null) {
|
||||
planMock = PlanBungeeMocker.setUp()
|
||||
.withDataFolder(tempDir.toFile())
|
||||
@ -51,7 +53,7 @@ public class BungeeMockComponent {
|
||||
return planMock;
|
||||
}
|
||||
|
||||
public PlanSystem getPlanSystem() throws Exception {
|
||||
public PlanSystem getPlanSystem() {
|
||||
if (component == null) {
|
||||
PlanBungee planMock = getPlanMock();
|
||||
component = DaggerPlanBungeeComponent.builder()
|
||||
|
@ -25,6 +25,7 @@ import net.md_5.bungee.api.plugin.PluginDescription;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import org.mockito.Mockito;
|
||||
import utilities.TestConstants;
|
||||
import utilities.TestResources;
|
||||
import utilities.mocks.objects.TestLogger;
|
||||
|
||||
import java.io.File;
|
||||
@ -38,9 +39,10 @@ import static org.mockito.Mockito.when;
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class PlanBungeeMocker extends Mocker {
|
||||
public class PlanBungeeMocker {
|
||||
|
||||
private PlanBungee planMock;
|
||||
private File tempFolder;
|
||||
|
||||
private PlanBungeeMocker() {
|
||||
}
|
||||
@ -51,7 +53,6 @@ public class PlanBungeeMocker extends Mocker {
|
||||
|
||||
private PlanBungeeMocker mockPlugin() {
|
||||
planMock = Mockito.mock(PlanBungee.class);
|
||||
super.planMock = planMock;
|
||||
|
||||
doReturn(new ColorScheme("§1", "§2", "§3")).when(planMock).getColorScheme();
|
||||
|
||||
@ -63,11 +64,12 @@ public class PlanBungeeMocker extends Mocker {
|
||||
}
|
||||
|
||||
PlanBungeeMocker withDataFolder(File tempFolder) {
|
||||
when(planMock.getDataFolder()).thenReturn(tempFolder);
|
||||
this.tempFolder = tempFolder;
|
||||
when(planMock.getDataFolder()).thenReturn(this.tempFolder);
|
||||
return this;
|
||||
}
|
||||
|
||||
PlanBungeeMocker withResourceFetchingFromJar() throws Exception {
|
||||
PlanBungeeMocker withResourceFetchingFromJar() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -91,7 +93,8 @@ public class PlanBungeeMocker extends Mocker {
|
||||
}
|
||||
|
||||
PlanBungeeMocker withPluginDescription() {
|
||||
File pluginYml = getFile("/bungee.yml");
|
||||
File pluginYml = tempFolder.toPath().resolve("jar").resolve("bungee.yml").toFile();
|
||||
TestResources.copyResourceIntoFile(pluginYml, "/bungee.yml");
|
||||
HashSet<String> empty = new HashSet<>();
|
||||
PluginDescription pluginDescription = new PluginDescription("Plan", "", "9.9.9", "AuroraLS3", empty, empty, pluginYml, "");
|
||||
when(planMock.getDescription()).thenReturn(pluginDescription);
|
||||
|
@ -1,74 +1,193 @@
|
||||
import dev.vankka.dependencydownload.task.GenerateDependencyDownloadResourceTask
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
plugins {
|
||||
id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion"
|
||||
id "com.github.node-gradle.node" version "3.4.0"
|
||||
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.2"
|
||||
}
|
||||
|
||||
configurations {
|
||||
// Runtime downloading scopes
|
||||
mysqlDriver
|
||||
sqliteDriver
|
||||
testImplementation.extendsFrom mysqlDriver, sqliteDriver
|
||||
compileOnly.extendsFrom mysqlDriver, sqliteDriver
|
||||
|
||||
swaggerJson // swagger.json configuration
|
||||
}
|
||||
|
||||
task generateResourceForMySQLDriver(type: GenerateDependencyDownloadResourceTask) {
|
||||
var conf = configurations.mysqlDriver
|
||||
configuration = conf
|
||||
file = "assets/plan/dependencies/" + conf.name + ".txt"
|
||||
// Not necessary to include in the resource
|
||||
includeShadowJarRelocations = false
|
||||
}
|
||||
|
||||
task generateResourceForSQLiteDriver(type: GenerateDependencyDownloadResourceTask) {
|
||||
var conf = configurations.sqliteDriver
|
||||
configuration = conf
|
||||
file = "assets/plan/dependencies/" + conf.name + ".txt"
|
||||
// Not necessary to include in the resource
|
||||
includeShadowJarRelocations = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
|
||||
implementation project(":api")
|
||||
compileOnly project(":extensions")
|
||||
implementation project(path: ":extensions", configuration: 'shadow')
|
||||
implementation "org.apache.commons:commons-text:$commonsTextVersion"
|
||||
implementation "org.apache.commons:commons-compress:$commonsCompressVersion"
|
||||
implementation "com.github.ben-manes.caffeine:caffeine:$caffeineVersion"
|
||||
implementation "mysql:mysql-connector-java:$mysqlVersion"
|
||||
implementation "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||
implementation "com.zaxxer:HikariCP:$hikariVersion"
|
||||
implementation "org.slf4j:slf4j-nop:$slf4jVersion"
|
||||
implementation "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
implementation "com.maxmind.geoip2:geoip2:$geoIpVersion"
|
||||
implementation "com.google.code.gson:gson:$gsonVersion"
|
||||
shadow project(":extensions")
|
||||
|
||||
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
|
||||
compileOnly "net.kyori:adventure-api:4.9.3"
|
||||
shadow("dev.vankka:dependencydownload-runtime:$dependencyDownloadVersion") {
|
||||
// Effectively disables relocating
|
||||
exclude module: "jar-relocator"
|
||||
}
|
||||
mysqlDriver "mysql:mysql-connector-java:$mysqlVersion"
|
||||
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||
|
||||
shadow "org.apache.commons:commons-text:$commonsTextVersion"
|
||||
shadow "org.apache.commons:commons-compress:$commonsCompressVersion"
|
||||
shadow "commons-codec:commons-codec:$commonsCodecVersion"
|
||||
shadow "com.github.ben-manes.caffeine:caffeine:$caffeineVersion"
|
||||
shadow "com.zaxxer:HikariCP:$hikariVersion"
|
||||
shadow "org.slf4j:slf4j-nop:$slf4jVersion"
|
||||
shadow "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
shadow "com.maxmind.geoip2:geoip2:$geoIpVersion"
|
||||
shadow "com.google.code.gson:gson:$gsonVersion"
|
||||
shadow "org.eclipse.jetty:jetty-server:$jettyVersion"
|
||||
shadow "org.eclipse.jetty:jetty-alpn-java-server:$jettyVersion"
|
||||
shadow "org.eclipse.jetty.http2:http2-server:$jettyVersion"
|
||||
shadow 'com.googlecode.json-simple:json-simple:1.1.1' // json simple used by UUIDFetcher
|
||||
|
||||
// Swagger annotations
|
||||
implementation "jakarta.ws.rs:jakarta.ws.rs-api:3.1.0"
|
||||
implementation "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion"
|
||||
implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:$swaggerVersion"
|
||||
|
||||
testImplementation project(":api")
|
||||
testImplementation "com.google.code.gson:gson:$gsonVersion"
|
||||
testImplementation "org.seleniumhq.selenium:selenium-java:4.4.0"
|
||||
testImplementation "org.testcontainers:testcontainers:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion"
|
||||
testImplementation "org.testcontainers:nginx:$testContainersVersion"
|
||||
}
|
||||
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
task updateVersion(type: Copy) {
|
||||
from('src/main/resources') {
|
||||
include 'plugin.yml'
|
||||
include 'bungee.yml'
|
||||
include 'nukkit.yml'
|
||||
include 'fabric.mod.json'
|
||||
}
|
||||
into 'build/sources/resources/'
|
||||
filter(ReplaceTokens, tokens: [version: '' + project.ext.fullVersion])
|
||||
}
|
||||
|
||||
node {
|
||||
download = true
|
||||
version = "16.14.2"
|
||||
nodeProjectDir = file("$rootDir/react/dashboard")
|
||||
}
|
||||
|
||||
task yarnBundle(type: YarnTask) {
|
||||
inputs.files(fileTree("$rootDir/react/dashboard/src"))
|
||||
inputs.file("$rootDir/react/dashboard/package.json")
|
||||
|
||||
outputs.dir("$rootDir/react/dashboard/build")
|
||||
|
||||
dependsOn yarn_install
|
||||
args = ['run', 'build']
|
||||
}
|
||||
|
||||
task copyYarnBuildResults {
|
||||
inputs.files(fileTree("$rootDir/react/dashboard/build"))
|
||||
outputs.dir("$rootDir/common/build/resources/main/assets/plan/web")
|
||||
|
||||
dependsOn yarnBundle
|
||||
doLast {
|
||||
mkdir "$rootDir/common/build/resources/main/assets/plan/web"
|
||||
copy {
|
||||
from "$rootDir/react/dashboard/build"
|
||||
into "$rootDir/common/build/resources/main/assets/plan/web"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task determineAssetModifications {
|
||||
inputs.files(fileTree(dir: 'src/main/resources/assets/plan/web'))
|
||||
inputs.files(fileTree(dir: 'src/main/resources/assets/plan/locale'))
|
||||
outputs.file("build/resources/main/assets/plan/AssetVersion.yml")
|
||||
|
||||
doLast {
|
||||
mkdir "build/resources/main/assets/plan"
|
||||
def versionFile = file("build/resources/main/assets/plan/AssetVersion.yml")
|
||||
versionFile.text = "" // Clear previous build
|
||||
ConfigurableFileTree tree = fileTree(dir: 'src/main/resources/assets/plan/web')
|
||||
tree.forEach { File f ->
|
||||
def gitModified = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'log', '-1', '--pretty=%ct', f.toString()
|
||||
standardOutput = gitModified
|
||||
}
|
||||
def gitModifiedAsString = gitModified.toString().strip()
|
||||
// git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds
|
||||
def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000
|
||||
def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree
|
||||
versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea
|
||||
"%s: %s\n", relativePath.toString().replace('.', ','), modified
|
||||
)
|
||||
}
|
||||
tree = fileTree(dir: 'src/main/resources/assets/plan/locale')
|
||||
tree.forEach { File f ->
|
||||
def gitModified = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine 'git', 'log', '-1', '--pretty=%ct', f.toString()
|
||||
standardOutput = gitModified
|
||||
}
|
||||
def gitModifiedAsString = gitModified.toString().strip()
|
||||
// git returns UNIX time in seconds, but most things in Java use UNIX time in milliseconds
|
||||
def modified = gitModifiedAsString.isEmpty() ? System.currentTimeMillis() : Long.parseLong(gitModifiedAsString) * 1000
|
||||
def relativePath = tree.getDir().toPath().relativize(f.toPath()) // File path relative to the tree
|
||||
versionFile.text += String.format( // writing YAML as raw text probably isn't the best idea
|
||||
"%s: %s\n", relativePath.toString().replace('.', ','), modified
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolve { // Swagger json generation task
|
||||
outputFileName = 'swagger'
|
||||
outputFormat = 'JSON'
|
||||
prettyPrint = 'TRUE'
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
buildClasspath = classpath
|
||||
resourcePackages = [
|
||||
'com.djrapitops.plan.delivery.webserver',
|
||||
'com.djrapitops.plan.delivery.webserver.resolver.auth',
|
||||
'com.djrapitops.plan.delivery.webserver.resolver.json',
|
||||
]
|
||||
outputDir = file('build/generated-resources/swagger/assets/plan/web/')
|
||||
}
|
||||
task swaggerJsonJar(type: Jar) {
|
||||
dependsOn resolve
|
||||
archiveClassifier.set("resolve")
|
||||
from 'build/generated-resources/swagger'
|
||||
}
|
||||
artifacts {
|
||||
swaggerJson swaggerJsonJar
|
||||
}
|
||||
|
||||
processResources {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
dependsOn copyYarnBuildResults
|
||||
dependsOn determineAssetModifications
|
||||
dependsOn generateResourceForMySQLDriver
|
||||
dependsOn generateResourceForSQLiteDriver
|
||||
dependsOn updateVersion
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
from 'build/sources/resources'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
dependsOn processResources
|
||||
|
||||
// Exclude these files
|
||||
exclude "**/*.svg"
|
||||
exclude "**/*.psd"
|
||||
|
||||
exclude "**/module-info.class"
|
||||
exclude "module-info.class"
|
||||
exclude 'META-INF/versions/' // Causes Sponge to crash
|
||||
exclude 'org/apache/http/**/*' // Unnecessary http client depended on by geolite2 implementation
|
||||
exclude 'mozilla/**/*'
|
||||
|
||||
// Exclude unnecessary SQLite drivers
|
||||
exclude '**/Linux/android-arm/libsqlitejdbc.so'
|
||||
exclude '**/DragonFlyBSD/**/libsqlitejdbc.so'
|
||||
|
||||
relocate 'com.google.protobuf', 'plan.com.mysql.cj.x.google.protobuf'
|
||||
|
||||
relocate 'com.maxmind', 'plan.com.maxmind'
|
||||
relocate 'com.fasterxml', 'plan.com.fasterxml'
|
||||
relocate 'com.zaxxer', 'plan.com.zaxxer'
|
||||
relocate 'com.google.gson', 'plan.com.google.gson'
|
||||
relocate 'com.google.errorprone', 'plan.com.google.errorprone'
|
||||
relocate 'org.bstats', 'plan.org.bstats'
|
||||
relocate 'org.slf4j', 'plan.org.slf4j'
|
||||
|
||||
// Exclude test dependencies
|
||||
exclude "org/junit/**/*"
|
||||
exclude "org/opentest4j/**/*"
|
||||
exclude "org/checkerframework/**/*"
|
||||
exclude "org/apiguardian/**/*"
|
||||
exclude "org/mockito/**/*"
|
||||
exclude "org/selenium/**/*"
|
||||
exclude "org/jayway/**/*"
|
||||
exclude "google/protobuf/**/*"
|
||||
exclude "jargs/gnu/**/*"
|
||||
}
|
||||
configurations = [project.configurations.shadow]
|
||||
}
|
||||
|
@ -17,67 +17,60 @@
|
||||
package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.utilities.java.TriConsumer;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Service for sourcing, mapping and consuming data.
|
||||
* <p>
|
||||
* The service is in charge of two data flows:
|
||||
* - push, given to consumers
|
||||
* - pull, obtained from sources
|
||||
* <p>
|
||||
* The mappers facilitate a one way type transformation if needed.
|
||||
* <p>
|
||||
* The interface is very abstract about how data is obtained,
|
||||
* but here are my general ideas of where the abstraction is coming from.
|
||||
* - push cause one or multiple consumers to modify stored data
|
||||
* - pull cause one or multiple suppliers to fetch stored data
|
||||
* - mappers are stateless type transformers in memory
|
||||
* <p>
|
||||
* Example use-case:
|
||||
* - PlayerJoinEvent is mapped to a generic event
|
||||
* - that generic event is then consumed and mapped until the data is in a database.
|
||||
* <p>
|
||||
* - Some kind of data is wanted to place on a web page
|
||||
* - It is requested and the suppliers and mappers give the wanted type of data.
|
||||
* <p>
|
||||
* Further research needed:
|
||||
* - Can this limited type system represent types of data that need parameters
|
||||
* (such as differentiate between two servers data)
|
||||
*/
|
||||
public interface DataService {
|
||||
|
||||
<M> DataService push(Class<M> type, M data);
|
||||
|
||||
<S> Optional<S> pull(Class<S> type);
|
||||
|
||||
<S, P> Optional<S> pull(Class<S> type, P parameter);
|
||||
|
||||
<A, B> B mapTo(Class<B> toType, A from);
|
||||
|
||||
default <S, P> Optional<S> pull(Class<S> type, Class<P> parameterType) {
|
||||
return pull(type, () -> pull(parameterType).orElse(null));
|
||||
default <K, T> void push(K identifier, T value) {
|
||||
push(identifier, value, (Class<T>) value.getClass());
|
||||
}
|
||||
|
||||
default <S, P> Optional<S> pull(Class<S> type, Supplier<P> parameter) {
|
||||
return pull(type, parameter.get());
|
||||
<K, T> void push(K identifier, T value, Class<T> type);
|
||||
|
||||
default <K, A, B> DataService registerOptionalMapper(Class<K> identifierType, Class<A> from, Class<B> to, BiFunction<K, A, Optional<B>> mapper) {
|
||||
return registerMapper(identifierType, from, to, (id, value) -> mapper.apply(id, value).orElse(null));
|
||||
}
|
||||
|
||||
<A, B> DataService registerMapper(Class<A> typeA, Class<B> typeB, Function<A, B> mapper);
|
||||
<K, A, B> DataService registerMapper(Class<K> identifierType, Class<A> from, Class<B> to, BiFunction<K, A, B> mapper);
|
||||
|
||||
<M> DataService registerConsumer(Class<M> type, Consumer<M> consumer);
|
||||
<K, A, B> DataService registerMapper(Class<K> identifierType, Class<A> from, Class<B> to, Function<A, B> mapper);
|
||||
|
||||
<S> DataService registerSupplier(Class<S> type, Supplier<S> supplier);
|
||||
default <K, A, B> DataService registerDataServiceMapper(Class<K> identifierType, Class<A> from, Class<B> to, BiFunction<DataService, A, B> mapper) {
|
||||
return registerMapper(identifierType, from, to, value -> mapper.apply(this, value));
|
||||
}
|
||||
|
||||
<P, S> DataService registerSupplier(Class<S> type, Class<P> parameterType, Function<P, S> supplierWithParameter);
|
||||
<K, Y, A, B> DataService registerMapper(Class<K> fromIdentifier, Class<A> from, Class<Y> toIdentifier, Class<B> to, TriConsumer<K, A, BiConsumer<Y, B>> mapper);
|
||||
|
||||
<P, S> DataService registerDBSupplier(Class<S> type, Class<P> parameterType, Function<P, Query<S>> supplierWithParameter);
|
||||
<K, T> DataService registerSink(Class<K> identifierType, Class<T> type, BiConsumer<K, T> consumer);
|
||||
|
||||
interface Mapping {
|
||||
<K, T> DataService registerDatabaseSink(Class<K> identifierType, Class<T> type, BiFunction<K, T, Transaction> consumer);
|
||||
|
||||
<K, T> Optional<T> pull(Class<T> type, K identifier);
|
||||
|
||||
<T> Optional<T> pullWithoutId(Class<T> type);
|
||||
|
||||
<K, T> DataService registerPullSource(Class<K> identifierType, Class<T> type, Function<K, T> source);
|
||||
|
||||
default <K, T> DataService registerOptionalPullSource(Class<K> identifierType, Class<T> type, Function<K, Optional<T>> source) {
|
||||
return registerPullSource(identifierType, type, id -> source.apply(id).orElse(null));
|
||||
}
|
||||
|
||||
<K, T> DataService registerDatabasePullSource(Class<K> identifierType, Class<T> type, Function<K, Query<T>> source);
|
||||
|
||||
<T> DataService registerPullSource(Class<T> type, Supplier<T> source);
|
||||
|
||||
<T> DataService registerDatabasePullSource(Class<T> type, Supplier<Query<T>> source);
|
||||
|
||||
<K, A, B> Optional<B> map(K identifier, A value, Class<B> toType);
|
||||
|
||||
interface Pipeline {
|
||||
void register(DataService service);
|
||||
}
|
||||
|
||||
|
@ -18,121 +18,133 @@ package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.Query;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.utilities.java.TriConsumer;
|
||||
import dagger.Lazy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Singleton
|
||||
public class DataSvc implements DataService {
|
||||
|
||||
private final MultiHashMap<Class, Mapper> mappers;
|
||||
private final MultiHashMap<Class, Mapper> mappersReverse;
|
||||
private final Map<Class, Supplier> suppliers;
|
||||
private final Map<ClassPair, Function> suppliersWithParameter;
|
||||
private final MultiHashMap<Class, Consumer> consumers;
|
||||
|
||||
private final Lazy<DBSystem> dbSystem;
|
||||
|
||||
private final Map<ClassPair, Function> pullSources;
|
||||
private final Map<Class, Supplier> noIdentifierPullSources;
|
||||
|
||||
private final MultiHashMap<ClassPair, BiConsumer> sinks;
|
||||
|
||||
private final MultiHashMap<ClassPair, Mapper> mappers;
|
||||
|
||||
@Inject
|
||||
public DataSvc(
|
||||
Lazy<DBSystem> dbSystem
|
||||
) {
|
||||
this.dbSystem = dbSystem;
|
||||
pullSources = new HashMap<>();
|
||||
noIdentifierPullSources = new HashMap<>();
|
||||
sinks = new MultiHashMap<>();
|
||||
mappers = new MultiHashMap<>();
|
||||
mappersReverse = new MultiHashMap<>();
|
||||
suppliers = new ConcurrentHashMap<>();
|
||||
suppliersWithParameter = new ConcurrentHashMap<>();
|
||||
consumers = new MultiHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A> DataService push(Class<A> type, A data) {
|
||||
if (data == null) return this;
|
||||
List<Mapper> mappers = this.mappers.get(type);
|
||||
for (Mapper mapper : mappers) {
|
||||
push(mapper.typeB, mapper.func.apply(data));
|
||||
public <K, T> void push(K identifier, T value, Class<T> type) {
|
||||
ClassPair<K, T> classPair = new ClassPair<>((Class<K>) identifier.getClass(), type);
|
||||
for (BiConsumer<K, T> sink : sinks.get(classPair)) {
|
||||
sink.accept(identifier, value);
|
||||
}
|
||||
List<Consumer> consumers = this.consumers.get(type);
|
||||
for (Consumer<A> consumer : consumers) {
|
||||
consumer.accept(data);
|
||||
}
|
||||
if (mappers.isEmpty() && consumers.isEmpty()) {
|
||||
System.out.println("WARN: Nothing consumed " + type);
|
||||
|
||||
for (Mapper mapper : mappers.get(classPair)) {
|
||||
push(identifier, mapper.func.apply(identifier, value), mapper.typeB);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, A, B> Optional<B> map(K identifier, A value, Class<B> toType) {
|
||||
ClassPair<K, A> classPair = new ClassPair<>((Class<K>) identifier.getClass(), (Class<A>) value.getClass());
|
||||
|
||||
List<Mapper> candidates = this.mappers.get(classPair);
|
||||
return candidates
|
||||
.stream()
|
||||
.filter(mapper -> Objects.equals(mapper.typeB, toType))
|
||||
.findAny()
|
||||
.map(mapper -> toType.cast(mapper.func.apply(identifier, value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, A, B> DataService registerMapper(Class<K> identifierType, Class<A> from, Class<B> to, BiFunction<K, A, B> mapper) {
|
||||
ClassPair<K, A> classPair = new ClassPair<>(identifierType, from);
|
||||
mappers.putOne(classPair, new Mapper<>(from, to, mapper));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> pull(Class<T> type) {
|
||||
Supplier<T> present = this.suppliers.get(type);
|
||||
if (present != null) return Optional.ofNullable(present.get());
|
||||
|
||||
List<Mapper> mappers = this.mappersReverse.get(type);
|
||||
for (Mapper mapper : mappers) {
|
||||
Optional<T> found = pull(mapper.typeA).map(mapper.func);
|
||||
if (found.isPresent()) return found;
|
||||
}
|
||||
|
||||
System.out.println("WARN: Nothing supplied " + type);
|
||||
return Optional.empty();
|
||||
public <K, A, B> DataService registerMapper(Class<K> identifierType, Class<A> from, Class<B> to, Function<A, B> mapper) {
|
||||
return registerMapper(identifierType, from, to, (id, value) -> mapper.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, B> B mapTo(Class<B> toType, A from) {
|
||||
List<Mapper> mappers = this.mappers.get(from.getClass());
|
||||
for (Mapper mapper : mappers) {
|
||||
if (mapper.typeB.equals(toType)) {
|
||||
return toType.cast(mapper.func);
|
||||
}
|
||||
}
|
||||
// TODO Figure out type mapping resolution when it needs more than one mapping
|
||||
System.out.println("WARN: No mapper for " + from.getClass() + " -> " + toType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, B> DataService registerMapper(Class<A> typeA, Class<B> typeB, Function<A, B> mapper) {
|
||||
Mapper<A, B> asWrapper = new Mapper<>(typeA, typeB, mapper);
|
||||
// TODO Prevent two mappers for same 2 types with a data structure
|
||||
mappers.putOne(typeA, asWrapper);
|
||||
mappersReverse.putOne(typeB, asWrapper);
|
||||
public <K, Y, A, B> DataService registerMapper(Class<K> fromIdentifier, Class<A> from, Class<Y> toIdentifier, Class<B> to, TriConsumer<K, A, BiConsumer<Y, B>> mapper) {
|
||||
ClassPair<K, A> classPair = new ClassPair<>(fromIdentifier, from);
|
||||
sinks.putOne(classPair, (id, value) -> mapper.accept(fromIdentifier.cast(id), from.cast(value), this::push));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A> DataService registerConsumer(Class<A> type, Consumer<A> consumer) {
|
||||
consumers.putOne(type, consumer);
|
||||
public <K, T> DataService registerSink(Class<K> identifierType, Class<T> type, BiConsumer<K, T> consumer) {
|
||||
ClassPair<K, T> classPair = new ClassPair<>(identifierType, type);
|
||||
sinks.putOne(classPair, consumer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A> DataService registerSupplier(Class<A> type, Supplier<A> supplier) {
|
||||
suppliers.put(type, supplier);
|
||||
public <K, T> DataService registerDatabaseSink(Class<K> identifierType, Class<T> type, BiFunction<K, T, Transaction> consumer) {
|
||||
return registerSink(identifierType, type, (id, value) -> dbSystem.get().getDatabase().executeTransaction(consumer.apply(id, value)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, T> Optional<T> pull(Class<T> type, K identifier) {
|
||||
ClassPair<K, T> classPair = new ClassPair<>((Class<K>) identifier.getClass(), type);
|
||||
return Optional.ofNullable(pullSources.get(classPair))
|
||||
.map(source -> source.apply(identifier))
|
||||
.map(type::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> pullWithoutId(Class<T> type) {
|
||||
return Optional.ofNullable(noIdentifierPullSources.get(type))
|
||||
.map(Supplier::get)
|
||||
.map(type::cast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, T> DataService registerPullSource(Class<K> identifierType, Class<T> type, Function<K, T> source) {
|
||||
ClassPair<K, T> classPair = new ClassPair<>(identifierType, type);
|
||||
pullSources.put(classPair, source);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S, P> Optional<S> pull(Class<S> type, P parameter) {
|
||||
if (parameter == null) return Optional.empty();
|
||||
Function<P, S> function = suppliersWithParameter.get(new ClassPair<>(type, parameter.getClass()));
|
||||
return function != null ? Optional.of(function.apply(parameter)) : Optional.empty();
|
||||
public <K, T> DataService registerDatabasePullSource(Class<K> identifierType, Class<T> type, Function<K, Query<T>> source) {
|
||||
return registerPullSource(identifierType, type, identifier -> dbSystem.get().getDatabase().query(source.apply(identifier)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, S> DataService registerSupplier(Class<S> type, Class<P> parameterType, Function<P, S> supplierWithParameter) {
|
||||
suppliersWithParameter.put(new ClassPair<>(type, parameterType), supplierWithParameter);
|
||||
public <T> DataService registerPullSource(Class<T> type, Supplier<T> source) {
|
||||
noIdentifierPullSources.put(type, source);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P, S> DataService registerDBSupplier(Class<S> type, Class<P> parameterType, Function<P, Query<S>> queryVisitor) {
|
||||
return registerSupplier(type, parameterType, parameter -> dbSystem.get().getDatabase().query(queryVisitor.apply(parameter)));
|
||||
public <T> DataService registerDatabasePullSource(Class<T> type, Supplier<Query<T>> source) {
|
||||
return registerPullSource(type, () -> dbSystem.get().getDatabase().query(source.get()));
|
||||
}
|
||||
|
||||
private static class ClassPair<A, B> {
|
||||
@ -159,16 +171,6 @@ public class DataSvc implements DataService {
|
||||
}
|
||||
}
|
||||
|
||||
private static class KeyValuePair<K, V> {
|
||||
final K key;
|
||||
final V value;
|
||||
|
||||
public KeyValuePair(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static class MultiHashMap<A, B> extends ConcurrentHashMap<A, List<B>> {
|
||||
|
||||
void putOne(A key, B value) {
|
||||
@ -176,15 +178,14 @@ public class DataSvc implements DataService {
|
||||
values.add(value);
|
||||
put(key, values);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Mapper<A, B> {
|
||||
private static class Mapper<K, A, B> {
|
||||
final Class<A> typeA;
|
||||
final Class<B> typeB;
|
||||
final Function<A, B> func;
|
||||
final BiFunction<K, A, B> func;
|
||||
|
||||
public Mapper(Class<A> typeA, Class<B> typeB, Function<A, B> func) {
|
||||
public Mapper(Class<A> typeA, Class<B> typeB, BiFunction<K, A, B> func) {
|
||||
this.typeA = typeA;
|
||||
this.typeB = typeB;
|
||||
this.func = func;
|
||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan;
|
||||
|
||||
import com.djrapitops.plan.commands.use.ColorScheme;
|
||||
import com.djrapitops.plan.commands.use.Subcommand;
|
||||
import net.playeranalytics.plugin.PluginInformation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@ -45,7 +46,10 @@ public interface PlanPlugin {
|
||||
|
||||
void onDisable();
|
||||
|
||||
@Deprecated
|
||||
/**
|
||||
* @deprecated Use {@code @Named("dataFolder") File}, or {@link PluginInformation#getDataFolder()}
|
||||
*/
|
||||
@Deprecated(since = "2021-03-09")
|
||||
File getDataFolder();
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package com.djrapitops.plan;
|
||||
import com.djrapitops.plan.api.PlanAPI;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.delivery.export.ExportSystem;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.web.ResolverSvc;
|
||||
import com.djrapitops.plan.delivery.web.ResourceSvc;
|
||||
import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker;
|
||||
@ -55,6 +56,8 @@ import javax.inject.Singleton;
|
||||
@Singleton
|
||||
public class PlanSystem implements SubSystem {
|
||||
|
||||
private static final long SERVER_ENABLE_TIME = System.currentTimeMillis();
|
||||
|
||||
private boolean enabled = false;
|
||||
|
||||
private final PlanFiles files;
|
||||
@ -134,21 +137,39 @@ public class PlanSystem implements SubSystem {
|
||||
this.logger = logger;
|
||||
this.errorLogger = errorLogger;
|
||||
|
||||
logger.info("");
|
||||
logger.info("§2");
|
||||
logger.info("§2 ██▌");
|
||||
logger.info("§2 ██▌ ██▌");
|
||||
logger.info("§2 ██▌██▌██▌██▌ §2Player Analytics");
|
||||
logger.info("§2 ██▌██▌██▌██▌ §fv" + versionChecker.getCurrentVersion());
|
||||
logger.info("");
|
||||
logger.info("§2");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static long getServerEnableTime() {
|
||||
return SERVER_ENABLE_TIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link com.djrapitops.plan.delivery.webserver.Addresses} instead.
|
||||
*/
|
||||
@Deprecated(since = "Addresses.java")
|
||||
public String getMainAddress() {
|
||||
return webServerSystem.getAddresses().getMainAddress().orElse(webServerSystem.getAddresses().getFallbackLocalhostAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
/**
|
||||
* Enables only the systems that are required for {@link com.djrapitops.plan.commands.PlanCommand}.
|
||||
*
|
||||
* @see #enableOtherThanCommands()
|
||||
*/
|
||||
public void enableForCommands() {
|
||||
enableSystems(configSystem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables the rest of the systems that are not enabled in {@link #enableForCommands()}.
|
||||
*/
|
||||
public void enableOtherThanCommands() {
|
||||
extensionService.register();
|
||||
resolverService.register();
|
||||
resourceService.register();
|
||||
@ -159,7 +180,6 @@ public class PlanSystem implements SubSystem {
|
||||
|
||||
enableSystems(
|
||||
files,
|
||||
configSystem,
|
||||
localeSystem,
|
||||
versionChecker,
|
||||
databaseSystem,
|
||||
@ -182,6 +202,15 @@ public class PlanSystem implements SubSystem {
|
||||
|
||||
extensionService.registerExtensions();
|
||||
enabled = true;
|
||||
|
||||
String javaVersion = System.getProperty("java.specification.version");
|
||||
if ("1.8".equals(javaVersion) || "9".equals(javaVersion) || "10".equals(javaVersion)
|
||||
) {
|
||||
logger.warn("! ------- Deprecation warning ------- !");
|
||||
logger.warn("Plan version 5.5 will require Java 11 or newer,");
|
||||
logger.warn("consider updating your JVM as soon as possible.");
|
||||
logger.warn("! ----------------------------------- !");
|
||||
}
|
||||
}
|
||||
|
||||
private void enableSystems(SubSystem... systems) {
|
||||
@ -191,23 +220,9 @@ public class PlanSystem implements SubSystem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
disableSystems(
|
||||
taskSystem,
|
||||
cacheSystem,
|
||||
listenerSystem,
|
||||
importSystem,
|
||||
exportSystem,
|
||||
processing,
|
||||
databaseSystem,
|
||||
webServerSystem,
|
||||
serverInfo,
|
||||
localeSystem,
|
||||
configSystem,
|
||||
files,
|
||||
versionChecker
|
||||
);
|
||||
public void enable() {
|
||||
enableForCommands();
|
||||
enableOtherThanCommands();
|
||||
}
|
||||
|
||||
private void disableSystems(SubSystem... systems) {
|
||||
@ -288,14 +303,27 @@ public class PlanSystem implements SubSystem {
|
||||
return extensionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Originally visible for testing purposes.
|
||||
*
|
||||
* @return the error logger of the system
|
||||
* @deprecated A smell, dagger should be used to construct things instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public ErrorLogger getErrorLogger() {
|
||||
return errorLogger;
|
||||
@Override
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
Formatters.clearSingleton();
|
||||
|
||||
extensionService.disableUpdates();
|
||||
|
||||
disableSystems(
|
||||
taskSystem,
|
||||
cacheSystem,
|
||||
listenerSystem,
|
||||
importSystem,
|
||||
exportSystem,
|
||||
processing,
|
||||
databaseSystem,
|
||||
webServerSystem,
|
||||
serverInfo,
|
||||
localeSystem,
|
||||
configSystem,
|
||||
files,
|
||||
versionChecker
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ import java.util.stream.Collectors;
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
*/
|
||||
@Singleton
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = true, since = "5.0")
|
||||
public class CommonAPI implements PlanAPI {
|
||||
|
||||
private final DBSystem dbSystem;
|
||||
@ -107,7 +107,7 @@ public class CommonAPI implements PlanAPI {
|
||||
|
||||
@Override
|
||||
public ServerContainer fetchServerContainer(UUID serverUUID) {
|
||||
return new ServerContainer(new com.djrapitops.plan.delivery.domain.container.ServerContainer());
|
||||
return new ServerContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,7 +37,7 @@ import java.util.UUID;
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public interface PlanAPI {
|
||||
|
||||
static PlanAPI getInstance() {
|
||||
|
@ -31,7 +31,7 @@ import java.util.Optional;
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public class PlayerContainer {
|
||||
|
||||
private final com.djrapitops.plan.delivery.domain.container.PlayerContainer container;
|
||||
|
@ -24,23 +24,18 @@ import java.util.Optional;
|
||||
* Wrapper for a ServerContainer.
|
||||
* <p>
|
||||
* The actual object is wrapped to avoid exposing too much API that might change.
|
||||
* See {@link com.djrapitops.plan.delivery.domain.keys.ServerKeys} for Key objects.
|
||||
* <p>
|
||||
* The Keys might change in the future, but the Optional API should help dealing with those cases.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
* @deprecated Plan API v4 has been deprecated, use the APIv5 instead (https://github.com/plan-player-analytics/Plan/wiki/APIv5).
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(forRemoval = true, since = "5.0")
|
||||
public class ServerContainer {
|
||||
|
||||
private final com.djrapitops.plan.delivery.domain.container.ServerContainer container;
|
||||
|
||||
public ServerContainer(com.djrapitops.plan.delivery.domain.container.ServerContainer container) {
|
||||
this.container = container;
|
||||
}
|
||||
public ServerContainer() {/*Empty constructor, no-op derpecated api class*/}
|
||||
|
||||
public <T> Optional<T> getValue(Key<T> key) {
|
||||
return container.getValue(key);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
@ -332,12 +332,36 @@ public class PlanCommand {
|
||||
.subcommand(clearCommand())
|
||||
.subcommand(removeCommand())
|
||||
.subcommand(uninstalledCommand())
|
||||
.subcommand(removeJoinAddressesCommand())
|
||||
.subcommand(onlineUuidMigration())
|
||||
.requirePermission(Permissions.DATA_BASE)
|
||||
.description(locale.getString(HelpLang.DB))
|
||||
.inDepthDescription(locale.getString(DeepHelpLang.DB))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Subcommand onlineUuidMigration() {
|
||||
return Subcommand.builder()
|
||||
.aliases("migrate_to_online_uuids", "migratetoonlineuuids")
|
||||
.requirePermission(Permissions.DATA_CLEAR)
|
||||
.optionalArgument("--remove_offline", "Remove offline players if given")
|
||||
.description(locale.getString(HelpLang.ONLINE_UUID_MIGRATION))
|
||||
.inDepthDescription("Moves and combines offline uuid data to online uuids where possible. Leaves offline-only players to database.")
|
||||
.onCommand((sender, arguments) -> databaseCommands.onOnlineConversion(commandName, sender, arguments))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Subcommand removeJoinAddressesCommand() {
|
||||
return Subcommand.builder()
|
||||
.aliases("remove_join_addresses", "removejoinaddresses")
|
||||
.requirePermission(Permissions.DATA_CLEAR)
|
||||
.requiredArgument(locale.getString(HelpLang.ARG_SERVER), locale.getString(HelpLang.DESC_ARG_SERVER_IDENTIFIER))
|
||||
.description(locale.getString(HelpLang.JOIN_ADDRESS_REMOVAL))
|
||||
.onCommand((sender, arguments) -> databaseCommands.onFixFabricJoinAddresses(commandName, sender, arguments))
|
||||
.onTabComplete(this::serverNames)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Subcommand backupCommand() {
|
||||
return Subcommand.builder()
|
||||
.aliases("backup")
|
||||
@ -369,7 +393,7 @@ public class PlanCommand {
|
||||
return DBType.names();
|
||||
}
|
||||
Optional<String> firstArgument = arguments.get(0);
|
||||
if (!firstArgument.isPresent()) {
|
||||
if (firstArgument.isEmpty()) {
|
||||
return tabCompleteCache.getMatchingBackupFilenames(null);
|
||||
}
|
||||
String part = firstArgument.get();
|
||||
|
@ -20,6 +20,7 @@ import com.djrapitops.plan.SubSystem;
|
||||
import com.djrapitops.plan.delivery.domain.auth.User;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.UserIdentifierQueries;
|
||||
@ -39,6 +40,7 @@ import java.util.stream.Collectors;
|
||||
@Singleton
|
||||
public class TabCompleteCache implements SubSystem {
|
||||
|
||||
private final Processing processing;
|
||||
private final PlanFiles files;
|
||||
private final DBSystem dbSystem;
|
||||
|
||||
@ -49,9 +51,11 @@ public class TabCompleteCache implements SubSystem {
|
||||
|
||||
@Inject
|
||||
public TabCompleteCache(
|
||||
Processing processing,
|
||||
PlanFiles files,
|
||||
DBSystem dbSystem
|
||||
) {
|
||||
this.processing = processing;
|
||||
this.files = files;
|
||||
this.dbSystem = dbSystem;
|
||||
playerIdentifiers = new ArrayList<>();
|
||||
@ -62,15 +66,17 @@ public class TabCompleteCache implements SubSystem {
|
||||
|
||||
@Override
|
||||
public void enable() {
|
||||
refreshPlayerIdentifiers();
|
||||
refreshServerIdentifiers();
|
||||
refreshUserIdentifiers();
|
||||
refreshBackupFileNames();
|
||||
processing.submitNonCritical(() -> {
|
||||
refreshPlayerIdentifiers();
|
||||
refreshServerIdentifiers();
|
||||
refreshUserIdentifiers();
|
||||
refreshBackupFileNames();
|
||||
|
||||
Collections.sort(playerIdentifiers);
|
||||
Collections.sort(serverIdentifiers);
|
||||
Collections.sort(userIdentifiers);
|
||||
Collections.sort(backupFileNames);
|
||||
Collections.sort(playerIdentifiers);
|
||||
Collections.sort(serverIdentifiers);
|
||||
Collections.sort(userIdentifiers);
|
||||
Collections.sort(backupFileNames);
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshServerIdentifiers() {
|
||||
@ -109,22 +115,30 @@ public class TabCompleteCache implements SubSystem {
|
||||
}
|
||||
|
||||
public List<String> getMatchingServerIdentifiers(String searchFor) {
|
||||
if (searchFor == null || searchFor.isEmpty()) return serverIdentifiers;
|
||||
if (searchFor == null || searchFor.isEmpty()) {
|
||||
return serverIdentifiers.size() < 100 ? serverIdentifiers : Collections.emptyList();
|
||||
}
|
||||
return serverIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getMatchingPlayerIdentifiers(String searchFor) {
|
||||
if (searchFor == null || searchFor.isEmpty()) return playerIdentifiers;
|
||||
if (searchFor == null || searchFor.isEmpty()) {
|
||||
return playerIdentifiers.size() < 100 ? playerIdentifiers : Collections.emptyList();
|
||||
}
|
||||
return playerIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getMatchingUserIdentifiers(String searchFor) {
|
||||
if (searchFor == null || searchFor.isEmpty()) return userIdentifiers;
|
||||
if (searchFor == null || searchFor.isEmpty()) {
|
||||
return userIdentifiers.size() < 100 ? userIdentifiers : Collections.emptyList();
|
||||
}
|
||||
return userIdentifiers.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<String> getMatchingBackupFilenames(String searchFor) {
|
||||
if (searchFor == null || searchFor.isEmpty()) return backupFileNames;
|
||||
if (searchFor == null || searchFor.isEmpty()) {
|
||||
return backupFileNames.size() < 100 ? backupFileNames : Collections.emptyList();
|
||||
}
|
||||
return backupFileNames.stream().filter(identifier -> identifier.startsWith(searchFor)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class Confirmation {
|
||||
) {
|
||||
this.locale = locale;
|
||||
awaiting = Caffeine.newBuilder()
|
||||
.expireAfterWrite(90, TimeUnit.SECONDS)
|
||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -231,7 +231,7 @@ public class DataUtilityCommands {
|
||||
}
|
||||
|
||||
PlayerContainer player = dbSystem.getDatabase().query(ContainerFetchQueries.fetchPlayerContainer(playerUUID));
|
||||
if (!player.getValue(PlayerKeys.REGISTERED).isPresent()) {
|
||||
if (player.getValue(PlayerKeys.REGISTERED).isEmpty()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_PLAYER_NOT_FOUND_REGISTER, identifier));
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,16 @@ package com.djrapitops.plan.commands.subcommands;
|
||||
import com.djrapitops.plan.commands.use.Arguments;
|
||||
import com.djrapitops.plan.commands.use.CMDSender;
|
||||
import com.djrapitops.plan.commands.use.ColorScheme;
|
||||
import com.djrapitops.plan.commands.use.MessageBuilder;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatter;
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.exceptions.database.DBOpException;
|
||||
import com.djrapitops.plan.gathering.domain.BaseUser;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.Server;
|
||||
import com.djrapitops.plan.identification.ServerInfo;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.processing.Processing;
|
||||
import com.djrapitops.plan.query.QuerySvc;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.DatabaseSettings;
|
||||
@ -35,21 +39,24 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.DBType;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.SQLiteDB;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.BaseUserQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.database.transactions.BackupCopyTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveEverythingTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemovePlayerTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.SetServerAsUninstalledTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.Transaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.*;
|
||||
import com.djrapitops.plan.storage.database.transactions.patches.BadFabricJoinAddressValuePatch;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
import net.playeranalytics.plugin.player.UUIDFetcher;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Singleton
|
||||
public class DatabaseCommands {
|
||||
@ -66,8 +73,10 @@ public class DatabaseCommands {
|
||||
private final Identifiers identifiers;
|
||||
private final PluginStatusCommands statusCommands;
|
||||
private final ErrorLogger errorLogger;
|
||||
private final Processing processing;
|
||||
|
||||
private final Formatter<Long> timestamp;
|
||||
private final Formatter<Long> clock;
|
||||
|
||||
@Inject
|
||||
public DatabaseCommands(
|
||||
@ -83,7 +92,8 @@ public class DatabaseCommands {
|
||||
Formatters formatters,
|
||||
Identifiers identifiers,
|
||||
PluginStatusCommands statusCommands,
|
||||
ErrorLogger errorLogger
|
||||
ErrorLogger errorLogger,
|
||||
Processing processing
|
||||
) {
|
||||
this.locale = locale;
|
||||
this.confirmation = confirmation;
|
||||
@ -99,6 +109,8 @@ public class DatabaseCommands {
|
||||
this.errorLogger = errorLogger;
|
||||
|
||||
this.timestamp = formatters.iso8601NoClockLong();
|
||||
clock = formatters.clockLong();
|
||||
this.processing = processing;
|
||||
}
|
||||
|
||||
public void onBackup(CMDSender sender, Arguments arguments) {
|
||||
@ -323,6 +335,56 @@ public class DatabaseCommands {
|
||||
}
|
||||
}
|
||||
|
||||
public void onFixFabricJoinAddresses(String mainCommand, CMDSender sender, Arguments arguments) {
|
||||
String identifier = arguments.concatenate(" ");
|
||||
Optional<ServerUUID> serverUUID = identifiers.getServerUUID(identifier);
|
||||
if (serverUUID.isEmpty()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier));
|
||||
}
|
||||
|
||||
Database database = dbSystem.getDatabase();
|
||||
|
||||
if (sender.supportsChatEvents()) {
|
||||
sender.buildMessage()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine()
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM))
|
||||
.addPart("§2§l[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT))
|
||||
.addPart(" ")
|
||||
.addPart("§4§l[\u2718]").command("/" + mainCommand + " cancel").hover(locale.getString(CommandLang.CONFIRM_DENY))
|
||||
.send();
|
||||
} else {
|
||||
sender.buildMessage()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_JOIN_ADDRESS_REMOVAL, identifier, database.getType().getName())).newLine()
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("§a/" + mainCommand + " accept")
|
||||
.addPart(" ")
|
||||
.addPart("§c/" + mainCommand + " cancel")
|
||||
.send();
|
||||
}
|
||||
|
||||
confirmation.confirm(sender, choice -> {
|
||||
if (Boolean.TRUE.equals(choice)) {
|
||||
performJoinAddressRemoval(sender, serverUUID.get(), database);
|
||||
} else {
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void performJoinAddressRemoval(CMDSender sender, ServerUUID serverUUID, Database database) {
|
||||
try {
|
||||
sender.send(locale.getString(CommandLang.DB_WRITE, database.getType().getName()));
|
||||
database.executeTransaction(new BadFabricJoinAddressValuePatch(serverUUID))
|
||||
.thenRunAsync(() -> sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS)))
|
||||
.exceptionally(error -> {
|
||||
sender.send(locale.getString(CommandLang.PROGRESS_FAIL, error.getMessage()));
|
||||
return null;
|
||||
});
|
||||
} catch (DBOpException e) {
|
||||
sender.send(locale.getString(CommandLang.PROGRESS_FAIL, e.getMessage()));
|
||||
errorLogger.error(e, ErrorContext.builder().related(sender, database.getType().getName()).build());
|
||||
}
|
||||
}
|
||||
|
||||
public void onRemove(String mainCommand, CMDSender sender, Arguments arguments) {
|
||||
String identifier = arguments.concatenate(" ");
|
||||
UUID playerUUID = identifiers.getPlayerUUID(identifier);
|
||||
@ -387,7 +449,6 @@ public class DatabaseCommands {
|
||||
String identifier = arguments.concatenate(" ");
|
||||
Server server = dbSystem.getDatabase()
|
||||
.query(ServerQueries.fetchServerMatchingIdentifier(identifier))
|
||||
.filter(s -> !s.isProxy())
|
||||
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_SERVER_NOT_FOUND, identifier)));
|
||||
|
||||
if (server.getUuid().equals(serverInfo.getServerUUID())) {
|
||||
@ -420,4 +481,102 @@ public class DatabaseCommands {
|
||||
}
|
||||
statusCommands.onReload(sender);
|
||||
}
|
||||
|
||||
public void onOnlineConversion(String mainCommand, CMDSender sender, Arguments arguments) {
|
||||
boolean removeOfflinePlayers = arguments.get(0)
|
||||
.map("--remove_offline"::equals)
|
||||
.orElse(false);
|
||||
sender.send(locale.getString(CommandLang.PROGRESS_PREPARING));
|
||||
processing.submitNonCritical(() -> {
|
||||
Map<UUID, BaseUser> baseUsersByUUID = dbSystem.getDatabase().query(BaseUserQueries.fetchAllBaseUsersByUUID());
|
||||
List<String> playerNames = baseUsersByUUID.values().stream().map(BaseUser::getName).collect(Collectors.toList());
|
||||
sender.send("Performing lookup for " + playerNames.size() + " uuids from Mojang..");
|
||||
sender.send("Preparation estimated complete at: " + clock.apply(System.currentTimeMillis() + playerNames.size() * 100) + " (due to request rate limiting)");
|
||||
Map<String, UUID> onlineUUIDsOfPlayers = getUUIDViaUUIDFetcher(playerNames);
|
||||
|
||||
if (onlineUUIDsOfPlayers.isEmpty()) {
|
||||
sender.send(locale.getString(CommandLang.PROGRESS_FAIL, "Did not get any UUIDs from Mojang."));
|
||||
return;
|
||||
}
|
||||
|
||||
int totalProfiles = baseUsersByUUID.size();
|
||||
int offlineOnlyUsers = 0;
|
||||
int combine = 0;
|
||||
int move = 0;
|
||||
|
||||
List<Transaction> transactions = new ArrayList<>();
|
||||
|
||||
for (BaseUser user : baseUsersByUUID.values()) {
|
||||
String playerName = user.getName();
|
||||
UUID recordedUUID = user.getUuid();
|
||||
UUID actualUUID = onlineUUIDsOfPlayers.get(playerName);
|
||||
|
||||
if (actualUUID == null) {
|
||||
offlineOnlyUsers++;
|
||||
if (removeOfflinePlayers) transactions.add(new RemovePlayerTransaction(recordedUUID));
|
||||
continue;
|
||||
}
|
||||
if (recordedUUID == actualUUID) {
|
||||
continue;
|
||||
}
|
||||
BaseUser alreadyExistingProfile = baseUsersByUUID.get(actualUUID);
|
||||
if (alreadyExistingProfile == null) {
|
||||
move++;
|
||||
transactions.add(new ChangeUserUUIDTransaction(recordedUUID, actualUUID));
|
||||
} else {
|
||||
combine++;
|
||||
transactions.add(new CombineUserTransaction(recordedUUID, actualUUID));
|
||||
}
|
||||
}
|
||||
|
||||
MessageBuilder messageBuilder = sender.buildMessage()
|
||||
.addPart(colors.getMainColor() + "Moving to online-only UUIDs (irreversible):").newLine()
|
||||
.addPart(colors.getSecondaryColor() + " Total players in database: " + totalProfiles).newLine()
|
||||
.addPart(colors.getSecondaryColor() + (removeOfflinePlayers ? "Removing (no online UUID): " : " Offline only (no online UUID): ") + offlineOnlyUsers).newLine()
|
||||
.addPart(colors.getSecondaryColor() + " Moving to new UUID: " + move).newLine()
|
||||
.addPart(colors.getSecondaryColor() + " Combining offline and online profiles: " + combine).newLine()
|
||||
.newLine()
|
||||
.addPart(colors.getSecondaryColor() + " Estimated online UUID players in database after: " + (totalProfiles - combine - offlineOnlyUsers) + (removeOfflinePlayers ? "" : " (+" + offlineOnlyUsers + " offline)")).newLine()
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM));
|
||||
if (sender.supportsChatEvents()) {
|
||||
messageBuilder
|
||||
.addPart("§2§l[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT))
|
||||
.addPart(" ")
|
||||
.addPart("§4§l[\u2718]").command("/" + mainCommand + " cancel").hover(locale.getString(CommandLang.CONFIRM_DENY))
|
||||
.send();
|
||||
} else {
|
||||
messageBuilder
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("§a/" + mainCommand + " accept")
|
||||
.addPart(" ")
|
||||
.addPart("§c/" + mainCommand + " cancel")
|
||||
.send();
|
||||
}
|
||||
|
||||
confirmation.confirm(sender, choice -> {
|
||||
if (Boolean.TRUE.equals(choice)) {
|
||||
transactions.forEach(dbSystem.getDatabase()::executeTransaction);
|
||||
dbSystem.getDatabase().executeTransaction(new Transaction() {
|
||||
@Override
|
||||
protected void performOperations() {
|
||||
sender.send(locale.getString(CommandLang.PROGRESS_SUCCESS));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_DATA));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, UUID> getUUIDViaUUIDFetcher(List<String> playerNames) {
|
||||
try {
|
||||
return new UUIDFetcher(playerNames).call();
|
||||
} catch (Exception | NoClassDefFoundError failure) {
|
||||
errorLogger.error(failure, ErrorContext.builder()
|
||||
.related("Migrating offline uuids to online uuids")
|
||||
.build());
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ public class LinkCommands {
|
||||
String serversListed = dbSystem.getDatabase()
|
||||
.query(ServerQueries.fetchPlanServerInformationCollection())
|
||||
.stream().sorted()
|
||||
.map(server -> m + server.getId().orElse(0) + "::" + t + server.getName() + "::" + s + server.getUuid() + "\n")
|
||||
.map(server -> m + server.getId().orElse(0) + "::" + t + server.getName() + "::" + s + server.getUuid() + "::" + s + server.getPlanVersion() + "\n")
|
||||
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
|
||||
.toString();
|
||||
sender.buildMessage()
|
||||
@ -194,7 +194,7 @@ public class LinkCommands {
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.LINK_NETWORK))
|
||||
.apply(builder -> linkTo(builder, sender, address))
|
||||
.send();
|
||||
if (!dbSystem.getDatabase().query(ServerQueries.fetchProxyServerInformation()).isPresent()) {
|
||||
if (dbSystem.getDatabase().query(ServerQueries.fetchProxyServerInformation()).isEmpty()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.NOTIFY_NO_NETWORK));
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ import com.djrapitops.plan.settings.locale.lang.HelpLang;
|
||||
import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.WebUserQueries;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RegisterWebUserTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.RemoveWebUserTransaction;
|
||||
import com.djrapitops.plan.storage.database.transactions.commands.StoreWebUserTransaction;
|
||||
import com.djrapitops.plan.utilities.PassEncryptUtil;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorLogger;
|
||||
@ -158,7 +158,7 @@ public class RegistrationCommands {
|
||||
boolean userExists = database.query(WebUserQueries.fetchUser(username)).isPresent();
|
||||
if (userExists) throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_WEB_USER_EXISTS));
|
||||
|
||||
database.executeTransaction(new RegisterWebUserTransaction(user))
|
||||
database.executeTransaction(new StoreWebUserTransaction(user))
|
||||
.get(); // Wait for completion
|
||||
|
||||
sender.send(locale.getString(CommandLang.WEB_USER_REGISTER_SUCCESS, username));
|
||||
@ -177,31 +177,26 @@ public class RegistrationCommands {
|
||||
UUID playerUUID = sender.getUUID().orElse(null);
|
||||
|
||||
String username;
|
||||
if (!givenUsername.isPresent() && playerUUID != null) {
|
||||
Optional<User> found = database.query(WebUserQueries.fetchUser(playerUUID));
|
||||
if (!found.isPresent()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED));
|
||||
}
|
||||
username = found.get().getUsername();
|
||||
} else if (!givenUsername.isPresent()) {
|
||||
if (givenUsername.isEmpty() && playerUUID != null) {
|
||||
username = database.query(WebUserQueries.fetchUser(playerUUID))
|
||||
.map(User::getUsername)
|
||||
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED)));
|
||||
} else if (givenUsername.isEmpty()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, "<" + locale.getString(HelpLang.ARG_USERNAME) + ">"));
|
||||
} else {
|
||||
username = givenUsername.get();
|
||||
}
|
||||
|
||||
Optional<User> found = database.query(WebUserQueries.fetchUser(username));
|
||||
if (!found.isPresent()) {
|
||||
throw new IllegalArgumentException(locale.getString(FailReason.USER_DOES_NOT_EXIST));
|
||||
}
|
||||
User presentUser = found.get();
|
||||
boolean ownsTheUser = Objects.equals(playerUUID, presentUser.getLinkedToUUID());
|
||||
User user = database.query(WebUserQueries.fetchUser(username))
|
||||
.orElseThrow(() -> new IllegalArgumentException(locale.getString(FailReason.USER_DOES_NOT_EXIST)));
|
||||
boolean ownsTheUser = Objects.equals(playerUUID, user.getLinkedToUUID());
|
||||
if (!(ownsTheUser || sender.hasPermission(Permissions.UNREGISTER_OTHER.getPerm()))) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.USER_NOT_LINKED));
|
||||
}
|
||||
|
||||
if (sender.supportsChatEvents()) {
|
||||
sender.buildMessage()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, presentUser.getUsername(), presentUser.getLinkedTo())).newLine()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, user.getUsername(), user.getLinkedTo())).newLine()
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM))
|
||||
.addPart("§2§l[\u2714]").command("/" + mainCommand + " accept").hover(locale.getString(CommandLang.CONFIRM_ACCEPT))
|
||||
.addPart(" ")
|
||||
@ -209,7 +204,7 @@ public class RegistrationCommands {
|
||||
.send();
|
||||
} else {
|
||||
sender.buildMessage()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, presentUser.getUsername(), presentUser.getLinkedTo())).newLine()
|
||||
.addPart(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_UNREGISTER, user.getUsername(), user.getLinkedTo())).newLine()
|
||||
.addPart(colors.getTertiaryColor() + locale.getString(CommandLang.CONFIRM)).addPart("§a/" + mainCommand + " accept")
|
||||
.addPart(" ")
|
||||
.addPart("§c/" + mainCommand + " cancel")
|
||||
@ -219,7 +214,7 @@ public class RegistrationCommands {
|
||||
confirmation.confirm(sender, choice -> {
|
||||
if (Boolean.TRUE.equals(choice)) {
|
||||
try {
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.UNREGISTER, presentUser.getUsername()));
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.UNREGISTER, user.getUsername()));
|
||||
database.executeTransaction(new RemoveWebUserTransaction(username))
|
||||
.get(); // Wait for completion
|
||||
ActiveCookieStore.removeUserCookie(username);
|
||||
@ -230,19 +225,15 @@ public class RegistrationCommands {
|
||||
errorLogger.warn(e, ErrorContext.builder().related("unregister command", sender, sender.getPlayerName().orElse("console"), arguments).build());
|
||||
}
|
||||
} else {
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_UNREGISTER, presentUser.getUsername()));
|
||||
sender.send(colors.getMainColor() + locale.getString(CommandLang.CONFIRM_CANCELLED_UNREGISTER, user.getUsername()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void onLogoutCommand(CMDSender sender, Arguments arguments) {
|
||||
Optional<String> username = arguments.get(0);
|
||||
if (!username.isPresent()) {
|
||||
throw new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, locale.getString(HelpLang.ARG_USERNAME) + "/*"));
|
||||
}
|
||||
|
||||
String loggingOut = username.get();
|
||||
String loggingOut = arguments.get(0)
|
||||
.orElseThrow(() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, locale.getString(HelpLang.ARG_USERNAME) + "/*")));
|
||||
|
||||
if ("*".equals(loggingOut)) {
|
||||
activeCookieStore.removeAll();
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package com.djrapitops.plan.commands.use;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.event.ClickEvent;
|
||||
@ -24,16 +25,21 @@ import org.apache.commons.text.TextStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class VelocityMessageBuilder implements MessageBuilder {
|
||||
/**
|
||||
* Shared Adventure {@link MessageBuilder} shared by Velocity and Sponge.
|
||||
*/
|
||||
public class AdventureMessageBuilder implements MessageBuilder {
|
||||
|
||||
private final VelocityCMDSender sender;
|
||||
private final CMDSender sender;
|
||||
private final Audience audience;
|
||||
private final TextComponent.Builder builder;
|
||||
|
||||
// Store reference to previous component to properly add hover & click events
|
||||
private Component previousComponent;
|
||||
|
||||
public VelocityMessageBuilder(VelocityCMDSender sender) {
|
||||
AdventureMessageBuilder(CMDSender sender, Audience audience) {
|
||||
this.sender = sender;
|
||||
this.audience = audience;
|
||||
builder = Component.text();
|
||||
}
|
||||
|
||||
@ -112,6 +118,6 @@ public class VelocityMessageBuilder implements MessageBuilder {
|
||||
if (previousComponent != null) {
|
||||
builder.append(previousComponent);
|
||||
}
|
||||
sender.commandSource.sendMessage(builder.build());
|
||||
audience.sendMessage(builder.build());
|
||||
}
|
||||
}
|
@ -22,11 +22,11 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ChatFormatter {
|
||||
public interface ChatFormatter {
|
||||
|
||||
public abstract int getWidth(String part);
|
||||
int getWidth(String part);
|
||||
|
||||
public String table(String message, String separator) {
|
||||
default String table(String message, String separator) {
|
||||
String[] lines = StringUtils.split(message, '\n');
|
||||
List<String[]> rows = new ArrayList<>();
|
||||
Maximum.ForInteger rowWidth = new Maximum.ForInteger(0);
|
||||
@ -54,7 +54,7 @@ public abstract class ChatFormatter {
|
||||
return table.toString();
|
||||
}
|
||||
|
||||
public List<String[]> tableAsParts(String message, String separator) {
|
||||
default List<String[]> tableAsParts(String message, String separator) {
|
||||
String[] lines = StringUtils.split(message, '\n');
|
||||
List<String[]> rows = new ArrayList<>();
|
||||
Maximum.ForInteger rowWidth = new Maximum.ForInteger(0);
|
||||
|
@ -50,6 +50,21 @@ public class CommandWithSubcommands extends Subcommand {
|
||||
return subcommands.stream().filter(sender::hasAllPermissionsFor).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<Subcommand> getSubcommands() {
|
||||
return subcommands;
|
||||
}
|
||||
|
||||
public Optional<Subcommand> findSubCommand(Arguments arguments) {
|
||||
return arguments.get(0).flatMap(alias -> {
|
||||
for (Subcommand subcommand : subcommands) {
|
||||
if (subcommand.getAliases().contains(alias)) {
|
||||
return Optional.of(subcommand);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
public void onHelp(CMDSender sender, Arguments arguments) {
|
||||
List<Subcommand> hasPermissionFor = getPermittedSubcommands(sender);
|
||||
sender.buildMessage()
|
||||
|
@ -18,7 +18,7 @@ package com.djrapitops.plan.commands.use;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class ConsoleChatFormatter extends ChatFormatter {
|
||||
public class ConsoleChatFormatter implements ChatFormatter {
|
||||
|
||||
@Override
|
||||
public int getWidth(String part) {
|
||||
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.commands.use;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ConsoleMessageBuilder implements MessageBuilder {
|
||||
|
||||
private final StringBuilder stringBuilder;
|
||||
private final Consumer<String> messageBus;
|
||||
|
||||
public ConsoleMessageBuilder(Consumer<String> messageBus) {
|
||||
this.messageBus = messageBus;
|
||||
this.stringBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder addPart(String msg) {
|
||||
stringBuilder.append(msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder newLine() {
|
||||
return addPart("\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder link(String address) {
|
||||
return addPart(address);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder command(String command) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder hover(String text) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder hover(String... text) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder hover(Collection<String> text) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder indent(int spaces) {
|
||||
stringBuilder.append(" ".repeat(Math.max(0, spaces)));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageBuilder tabular(CharSequence columnSeparator) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send() {
|
||||
messageBus.accept(stringBuilder.toString());
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
package com.djrapitops.plan.commands.use;
|
||||
|
||||
public class PlayerChatFormatter extends ChatFormatter {
|
||||
public class PlayerChatFormatter implements ChatFormatter {
|
||||
|
||||
@Override
|
||||
public int getWidth(String part) {
|
||||
|
@ -29,7 +29,7 @@ import java.util.UUID;
|
||||
* @see InspectContainer
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public final class AnalysisContainer extends InspectContainer {
|
||||
|
||||
private final Map<String, Map<UUID, ? extends Serializable>> playerTableValues;
|
||||
|
@ -29,7 +29,7 @@ import java.util.TreeMap;
|
||||
* @see TableContainer
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public class InspectContainer {
|
||||
|
||||
protected final List<String> values;
|
||||
|
@ -28,7 +28,7 @@ import java.util.List;
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public class TableContainer {
|
||||
|
||||
protected final String[] header;
|
||||
|
@ -25,7 +25,7 @@ import java.util.UUID;
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public interface BanData {
|
||||
|
||||
boolean isBanned(UUID uuid);
|
||||
|
@ -35,7 +35,7 @@ import java.util.UUID;
|
||||
* @author AuroraLS3
|
||||
* @deprecated PluginData API has been deprecated - see https://github.com/plan-player-analytics/Plan/wiki/APIv5---DataExtension-API for new API.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public abstract class PluginData {
|
||||
|
||||
private final ContainerSize size;
|
||||
|
@ -24,6 +24,6 @@ import com.djrapitops.plan.delivery.domain.container.DynamicDataContainer;
|
||||
* @author AuroraLS3
|
||||
* @deprecated AnalysisContainer is no longer used.
|
||||
*/
|
||||
@Deprecated
|
||||
@Deprecated(since = "5.0")
|
||||
public class AnalysisContainer extends DynamicDataContainer {
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* This file is part of Player Analytics (Plan).
|
||||
*
|
||||
* Plan is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License v3 as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Plan is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.domain;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a single join address - number pair.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
public class JoinAddressCount implements Comparable<JoinAddressCount> {
|
||||
|
||||
private final int count;
|
||||
private String joinAddress;
|
||||
|
||||
public JoinAddressCount(Map.Entry<String, Integer> entry) {
|
||||
this(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
public JoinAddressCount(String joinAddress, int count) {
|
||||
this.joinAddress = joinAddress;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getJoinAddress() {
|
||||
return joinAddress;
|
||||
}
|
||||
|
||||
public void setJoinAddress(String joinAddress) {
|
||||
this.joinAddress = joinAddress;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JoinAddressCount other) {
|
||||
return String.CASE_INSENSITIVE_ORDER.compare(this.joinAddress, other.joinAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
JoinAddressCount that = (JoinAddressCount) o;
|
||||
return getCount() == that.getCount() && Objects.equals(getJoinAddress(), that.getJoinAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getJoinAddress(), getCount());
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user