├── .gitattributes ├── .github ├── checkstyle.xml └── workflows │ ├── gradle-check.yml │ ├── gradle-publish.yml │ └── gradle-test.yml ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTE.md ├── LICENSE ├── README.md ├── backups └── kotlin │ └── CreateTeam.kt ├── bin ├── main │ └── plugin.yml └── test │ ├── db.zst │ └── test-plugin-activelist.jar ├── build.gradle ├── crawl.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mcpm ├── mirrorlist.yml ├── requirements.txt ├── settings.gradle ├── src ├── main │ ├── java │ │ └── org │ │ │ └── hydev │ │ │ └── mcpm │ │ │ ├── Constants.java │ │ │ ├── SpigotEntry.kt │ │ │ ├── client │ │ │ ├── Downloader.java │ │ │ ├── arguments │ │ │ │ ├── ArgsParser.java │ │ │ │ ├── ArgsParserFactory.java │ │ │ │ ├── ControllerFactory.java │ │ │ │ ├── ControllerFactoryBoundary.java │ │ │ │ ├── HelpException.java │ │ │ │ ├── InteractorFactory.java │ │ │ │ ├── InteractorFactoryBoundary.java │ │ │ │ ├── ParserFactory.java │ │ │ │ ├── PrintHelpAction.java │ │ │ │ └── parsers │ │ │ │ │ ├── CommandConfigurator.java │ │ │ │ │ ├── CommandHandler.java │ │ │ │ │ ├── CommandParser.java │ │ │ │ │ ├── ExportPluginsParser.java │ │ │ │ │ ├── ImportParser.java │ │ │ │ │ ├── InfoParser.java │ │ │ │ │ ├── InstallParser.java │ │ │ │ │ ├── ListParser.java │ │ │ │ │ ├── LoadParser.java │ │ │ │ │ ├── MirrorParser.java │ │ │ │ │ ├── PageParser.java │ │ │ │ │ ├── RefreshParser.java │ │ │ │ │ ├── ReloadParser.java │ │ │ │ │ ├── SearchParser.java │ │ │ │ │ ├── UninstallParser.java │ │ │ │ │ ├── UnloadParser.java │ │ │ │ │ └── UpdateParser.java │ │ │ ├── commands │ │ │ │ ├── controllers │ │ │ │ │ ├── ExportController.java │ │ │ │ │ ├── ImportController.java │ │ │ │ │ ├── InfoController.java │ │ │ │ │ ├── InstallController.java │ │ │ │ │ ├── ListController.java │ │ │ │ │ ├── LoadController.java │ │ │ │ │ ├── MirrorController.java │ │ │ │ │ ├── PageBoundary.java │ │ │ │ │ ├── RefreshController.java │ │ │ │ │ ├── ReloadController.java │ │ │ │ │ ├── SearchPackagesController.java │ │ │ │ │ ├── UninstallController.java │ │ │ │ │ ├── UnloadController.java │ │ │ │ │ └── UpdateController.java │ │ │ │ └── presenters │ │ │ │ │ ├── ExportPresenter.java │ │ │ │ │ ├── ImportPresenter.java │ │ │ │ │ ├── InfoPresenter.java │ │ │ │ │ ├── InstallResultPresenter.java │ │ │ │ │ ├── ListResultPresenter.java │ │ │ │ │ ├── PagedPresenter.java │ │ │ │ │ ├── SearchResultPresenter.java │ │ │ │ │ ├── UninstallResultPresenter.java │ │ │ │ │ └── UpdatePresenter.java │ │ │ ├── database │ │ │ │ ├── fetcher │ │ │ │ │ ├── ConstantFetcher.java │ │ │ │ │ ├── DatabaseFetcher.java │ │ │ │ │ ├── DatabaseFetcherListener.java │ │ │ │ │ └── QuietFetcherListener.java │ │ │ │ ├── mirrors │ │ │ │ │ ├── Mirror.java │ │ │ │ │ └── MirrorSelectBoundary.java │ │ │ │ └── tracker │ │ │ │ │ ├── MockPluginTracker.java │ │ │ │ │ └── PluginTracker.java │ │ │ ├── display │ │ │ │ ├── ProgressBarBoundary.java │ │ │ │ ├── ProgressRowBoundary.java │ │ │ │ ├── ProgressSpeedBoundary.java │ │ │ │ ├── presenters │ │ │ │ │ ├── InstallPresenter.java │ │ │ │ │ ├── KvInfoPresenter.java │ │ │ │ │ ├── ListPresenter.java │ │ │ │ │ ├── LogExportPresenter.java │ │ │ │ │ ├── LogImportPresenter.java │ │ │ │ │ ├── LogUpdatePresenter.java │ │ │ │ │ ├── SearchPresenter.java │ │ │ │ │ ├── Table.java │ │ │ │ │ └── UninstallPresenter.java │ │ │ │ └── progress │ │ │ │ │ ├── ProgressBar.java │ │ │ │ │ ├── ProgressBarFetcherListener.java │ │ │ │ │ ├── ProgressBarTheme.java │ │ │ │ │ ├── ProgressRow.java │ │ │ │ │ └── ProgressSpeedCalculator.java │ │ │ ├── export │ │ │ │ ├── ExportInteractor.java │ │ │ │ ├── ExportModel.java │ │ │ │ ├── ExportPluginsBoundary.java │ │ │ │ ├── ExportPluginsInput.java │ │ │ │ ├── ExportPluginsResult.java │ │ │ │ ├── ImportInput.java │ │ │ │ ├── ImportInteractor.java │ │ │ │ ├── ImportPluginsBoundary.java │ │ │ │ ├── ImportResult.java │ │ │ │ └── storage │ │ │ │ │ ├── FixedFileStorage.java │ │ │ │ │ ├── PasteBinStorage.java │ │ │ │ │ ├── StringLiteralStorage.java │ │ │ │ │ ├── StringStorage.java │ │ │ │ │ └── StringStorageFactory.java │ │ │ ├── installer │ │ │ │ ├── InstallBoundary.java │ │ │ │ ├── InstallInteractor.java │ │ │ │ ├── PluginDownloader.java │ │ │ │ ├── SpigotPluginDownloader.java │ │ │ │ ├── input │ │ │ │ │ └── InstallInput.java │ │ │ │ └── output │ │ │ │ │ └── InstallResult.java │ │ │ ├── interaction │ │ │ │ ├── IUserInteractor.kt │ │ │ │ ├── SpigotUserHandler.kt │ │ │ │ └── StdInteractor.kt │ │ │ ├── list │ │ │ │ ├── ListAllBoundary.java │ │ │ │ ├── ListAllInteractor.java │ │ │ │ ├── ListResult.java │ │ │ │ ├── ListType.java │ │ │ │ ├── ListUpdatableBoundary.java │ │ │ │ └── ListUpdatableHelper.java │ │ │ ├── loader │ │ │ │ ├── LoadBoundary.java │ │ │ │ ├── LocalJarBoundary.java │ │ │ │ ├── LocalJarFinder.java │ │ │ │ ├── PluginLoader.java │ │ │ │ ├── PluginLoaderHelper.java │ │ │ │ ├── PluginNotFoundException.java │ │ │ │ ├── ReloadBoundary.java │ │ │ │ └── UnloadBoundary.java │ │ │ ├── local │ │ │ │ ├── LocalDatabaseFetcher.java │ │ │ │ ├── LocalPluginTracker.java │ │ │ │ ├── MirrorSelector.java │ │ │ │ ├── PageController.java │ │ │ │ └── PluginTrackerError.java │ │ │ ├── matcher │ │ │ │ ├── MatchPluginsBoundary.java │ │ │ │ ├── MatchPluginsInput.java │ │ │ │ ├── MatchPluginsInteractor.java │ │ │ │ ├── MatchPluginsResult.java │ │ │ │ ├── PluginModelId.java │ │ │ │ ├── PluginVersionId.java │ │ │ │ └── PluginVersionState.java │ │ │ ├── models │ │ │ │ ├── Database.java │ │ │ │ ├── PluginCommand.java │ │ │ │ ├── PluginModel.java │ │ │ │ ├── PluginTrackerModel.java │ │ │ │ ├── PluginVersion.java │ │ │ │ └── PluginYml.java │ │ │ ├── search │ │ │ │ ├── SearchInteractor.java │ │ │ │ ├── SearchPackagesBoundary.java │ │ │ │ ├── SearchPackagesInput.java │ │ │ │ ├── SearchPackagesResult.java │ │ │ │ ├── SearchPackagesType.java │ │ │ │ ├── Searcher.java │ │ │ │ ├── SearcherByCommand.java │ │ │ │ ├── SearcherByKeyword.java │ │ │ │ ├── SearcherByName.java │ │ │ │ └── SearcherFactory.java │ │ │ ├── uninstall │ │ │ │ ├── FileRemove.java │ │ │ │ ├── FileRemoveResult.java │ │ │ │ ├── PluginRemover.java │ │ │ │ ├── UninstallBoundary.java │ │ │ │ ├── UninstallInput.java │ │ │ │ ├── UninstallResult.java │ │ │ │ └── Uninstaller.java │ │ │ └── updater │ │ │ │ ├── CheckForUpdatesBoundary.java │ │ │ │ ├── CheckForUpdatesInput.java │ │ │ │ ├── CheckForUpdatesInteractor.java │ │ │ │ ├── CheckForUpdatesResult.java │ │ │ │ ├── UpdateBoundary.java │ │ │ │ ├── UpdateInput.java │ │ │ │ ├── UpdateInteractor.java │ │ │ │ ├── UpdateOutcome.java │ │ │ │ └── UpdateResult.java │ │ │ ├── server │ │ │ ├── SpigetCrawler.java │ │ │ └── spiget │ │ │ │ ├── CreateDatabase.java │ │ │ │ ├── SpigetResource.java │ │ │ │ └── SpigetVersion.java │ │ │ └── utils │ │ │ ├── ColorLogger.java │ │ │ ├── ConsoleUtils.java │ │ │ ├── GeneralUtils.java │ │ │ ├── HashUtils.java │ │ │ ├── NetworkUtils.java │ │ │ ├── Pair.java │ │ │ ├── PluginJarFile.java │ │ │ ├── ReflectionUtils.java │ │ │ ├── StoredHashMap.java │ │ │ ├── Sugar.java │ │ │ ├── TemporaryDir.java │ │ │ ├── UnitConverter.java │ │ │ ├── ZstdException.java │ │ │ ├── ZstdUtils.java │ │ │ └── arrays │ │ │ ├── FixedWindowSum.java │ │ │ └── SlidingWindow.java │ └── resources │ │ └── plugin.yml └── test │ ├── java │ └── org │ │ └── hydev │ │ └── mcpm │ │ ├── client │ │ ├── arguments │ │ │ ├── ArgsParserTest.java │ │ │ ├── ExportParserTest.java │ │ │ ├── InfoParserTest.java │ │ │ ├── InstallParserTest.java │ │ │ ├── ListParserTest.java │ │ │ ├── LoadParserTest.java │ │ │ ├── MirrorParserTest.java │ │ │ ├── ParserFactoryTest.java │ │ │ ├── RefreshParserTest.java │ │ │ ├── ReloadParserTest.java │ │ │ ├── SearchParserTest.java │ │ │ ├── UninstallParserTest.java │ │ │ ├── UnloadParserTest.java │ │ │ ├── UpdateParserTest.java │ │ │ └── mock │ │ │ │ ├── MockExportBoundary.java │ │ │ │ ├── MockInstallBoundary.java │ │ │ │ ├── MockListBoundary.java │ │ │ │ ├── MockLoadBoundary.java │ │ │ │ ├── MockMirrorBoundary.java │ │ │ │ ├── MockRefreshFetcher.java │ │ │ │ ├── MockReloadBoundary.java │ │ │ │ ├── MockSearchBoundary.java │ │ │ │ ├── MockSearchPresenter.java │ │ │ │ ├── MockUninstallBoundary.java │ │ │ │ ├── MockUnloadBoundary.java │ │ │ │ └── MockUpdateBoundary.java │ │ ├── commands │ │ │ └── presenter │ │ │ │ └── TableTest.java │ │ ├── database │ │ │ ├── CheckForUpdatesInteractorTest.java │ │ │ ├── MatchInteractorTest.java │ │ │ ├── MirrorSelectorTest.java │ │ │ ├── MockInstaller.java │ │ │ ├── MockPluginTracker.java │ │ │ ├── PluginMockFactory.java │ │ │ ├── SearchInteractorTest.java │ │ │ ├── SilentUpdatePresenter.java │ │ │ └── UpdateInteractorTest.java │ │ ├── export │ │ │ └── ExportInteractorTest.java │ │ ├── installer │ │ │ ├── InstallInteractorTest.java │ │ │ ├── MockDownloader.java │ │ │ └── MockLocalPluginTracker.java │ │ ├── local │ │ │ └── SuperLocalPluginTrackerTest.java │ │ └── models │ │ │ └── PluginYmlTest.java │ │ └── utils │ │ ├── ColorLoggerTest.java │ │ ├── ConsoleUtilsTest.java │ │ ├── GeneralUtilsTest.java │ │ ├── HashUtilsTest.java │ │ ├── NetworkUtilsTest.java │ │ ├── PairTest.java │ │ ├── PluginJarFileTest.java │ │ ├── ReflectionUtilsTest.java │ │ ├── StoredHashMapTest.java │ │ ├── SugarTest.java │ │ ├── TemporaryDirTest.java │ │ ├── UnitConverterTest.java │ │ ├── ZstdUtilsTest.java │ │ └── arrays │ │ └── SlidingWindowTest.java │ └── resources │ ├── db.zst │ ├── test-plugin-activelist.jar │ └── testing.jar └── tools ├── __init__.py ├── analyze_file.py ├── analyze_spiget.py ├── crawler ├── install.sh ├── mcprs-crawl.service └── mcprs-crawl.timer ├── download_jdk.py ├── gen_pyml_deps.py ├── mirror ├── docker-compose.yml ├── nginx │ ├── Dockerfile │ └── mcprs.conf └── sync │ ├── Dockerfile │ └── sync.py ├── run.py ├── start_server.py └── update_build.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/gradle-check.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Checkstyle CI 2 | 3 | on: [push] 4 | 5 | permissions: 6 | checks: write 7 | actions: read 8 | contents: read 9 | 10 | jobs: 11 | build: 12 | name: Gradle Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Java JDK 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '19' 21 | distribution: 'adopt' 22 | 23 | - name: Gradle Wrapper Validation 24 | uses: gradle/wrapper-validation-action@v1 25 | 26 | - name: Setup Gradle with Dependency Cache 27 | uses: gradle/gradle-build-action@v2.3.3 28 | 29 | - name: Gradle Checkstyle 30 | run: | 31 | ./gradlew checkStyleMain 32 | ./gradlew checkStyleTest 33 | -------------------------------------------------------------------------------- /.github/workflows/gradle-publish.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | permissions: 8 | checks: write 9 | actions: read 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | name: Gradle Test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Java JDK 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '19' 23 | distribution: 'adopt' 24 | 25 | - name: Gradle Wrapper Validation 26 | uses: gradle/wrapper-validation-action@v1 27 | 28 | - name: Setup Gradle with Dependency Cache 29 | uses: gradle/gradle-build-action@v2.3.3 30 | 31 | - name: Gradle Build 32 | run: ./gradlew build --scan 33 | 34 | - name: Gradle Test 35 | run: ./gradlew test 36 | 37 | - name: Publish Artifacts 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: mcpm 41 | path: build/libs 42 | 43 | - name: Publish Maven Package to Github Package Registry 44 | run: ./gradlew publish 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | 48 | 49 | -------------------------------------------------------------------------------- /.github/workflows/gradle-test.yml: -------------------------------------------------------------------------------- 1 | name: Gradle Test CI 2 | 3 | on: [push] 4 | 5 | permissions: 6 | checks: write 7 | actions: read 8 | contents: read 9 | 10 | jobs: 11 | build: 12 | name: Gradle Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Set up Java JDK 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '19' 21 | distribution: 'adopt' 22 | 23 | - name: Gradle Wrapper Validation 24 | uses: gradle/wrapper-validation-action@v1 25 | 26 | - name: Setup Gradle with Dependency Cache 27 | uses: gradle/gradle-build-action@v2.3.3 28 | 29 | - name: Gradle Test 30 | run: ./gradlew test 31 | 32 | - name: Publish Artifacts 33 | uses: actions/upload-artifact@v3 34 | with: 35 | name: mcpm 36 | path: build/libs 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | replay_pid* 24 | 25 | .DS_Store 26 | ._* 27 | 28 | .idea 29 | build 30 | .gradle 31 | data 32 | .mcpm 33 | *.pyc 34 | plugins 35 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.configuration.updateBuildConfiguration": "interactive", 3 | "java.checkstyle.configuration": "${workspaceFolder}\\.github\\checkstyle.xml", 4 | "java.checkstyle.version": "10.5.0" 5 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Hykilpikonna 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/main/plugin.yml: -------------------------------------------------------------------------------- 1 | main: org.hydev.mcpm.SpigotEntry 2 | name: MCPM 3 | version: '@version@' 4 | description: Minecraft Package Manager for Bukkit/Spiget Servers 5 | api-version: 1.18 6 | authors: [Hykilpikonna, 1whatleytay, MstrPikachu, kchprog, jerryzhu509, thudoan1706, 7 | aanushkasharma] 8 | website: https://github.com/CSC207-2022F-UofT/mcpm 9 | commands: 10 | mcpm: 11 | description: Minecraft package manager 12 | usage: 'View help: /mcpm -h' 13 | permission: mcpm.use 14 | libraries: 15 | - net.sourceforge.argparse4j:argparse4j:0.9.0 16 | - org.apache.httpcomponents.client5:httpclient5-fluent:5.2-beta1 17 | - com.fasterxml.jackson.core:jackson-core:2.14.0 18 | - com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 19 | - com.google.guava:guava:31.1-jre 20 | - org.jetbrains:annotations:23.0.0 21 | - org.fusesource.jansi:jansi:2.4.0 22 | - org.apache.commons:commons-lang3:3.12.0 23 | - com.opencsv:opencsv:5.7.1 24 | - com.github.luben:zstd-jni:1.5.2-5 25 | - io.airlift:aircompressor:0.21 26 | -------------------------------------------------------------------------------- /bin/test/db.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/bin/test/db.zst -------------------------------------------------------------------------------- /bin/test/test-plugin-activelist.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/bin/test/test-plugin-activelist.jar -------------------------------------------------------------------------------- /crawl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # Get script dir 5 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 6 | pushd "$SCRIPT_DIR" 7 | 8 | PY="/home/azalea/.conda/envs/310/bin/python3" 9 | 10 | # Crawl packages 11 | $PY -m tools.run org.hydev.mcpm.server.SpigetCrawler 12 | 13 | # Create database 14 | $PY -m tools.run org.hydev.mcpm.server.spiget.CreateDatabase 15 | 16 | # Zstd compression 17 | rm -rf .mcpm/db.zst 18 | zstd -f -19 -T36 .mcpm/db 19 | 20 | popd 21 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-rc-3-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /mcpm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ./gradlew :compileJava 6 | classpath=$(./gradlew :printCp 2>& 1>/dev/null) 7 | 8 | java -cp "$classpath" org.hydev.mcpm.client.arguments.ArgsParser "$@" 9 | -------------------------------------------------------------------------------- /mirrorlist.yml: -------------------------------------------------------------------------------- 1 | - 2 | host: mcprs.hydev.org 3 | protocols: [http, https, rsyncd] 4 | tier: 0 5 | country: Canada 6 | speed: 100 7 | interval: 24 8 | httpEndpoint: / 9 | rsyncEndpoint: /mcprs 10 | - 11 | host: mcprs-bell.hydev.org 12 | protocols: [http, https, rsyncd] 13 | tier: 0 14 | country: Canada 15 | speed: 750 16 | interval: 24 17 | httpEndpoint: / 18 | rsyncEndpoint: /mcprs 19 | - 20 | host: mcprs-lux.hydev.org 21 | protocols: [http, rsyncd] 22 | tier: 1 23 | country: Luxembourg 24 | speed: 200 25 | interval: 24 26 | httpEndpoint: / 27 | rsyncEndpoint: /mcprs 28 | - 29 | host: mcprs-tokyo.hydev.org 30 | protocols: [http, https, rsyncd] 31 | tier: 1 32 | country: Tokyo 33 | speed: 200 34 | interval: 24 35 | httpEndpoint: / 36 | rsyncEndpoint: /mcprs 37 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | hypy_utils>=1.0.16 3 | py-cpuinfo 4 | tqdm 5 | ruamel.yaml 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'mcpm' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/Constants.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 5 | 6 | import java.io.File; 7 | 8 | import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY; 9 | import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_INVALID_SUBTYPE; 10 | import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; 11 | import static com.fasterxml.jackson.databind.DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS; 12 | import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; 13 | 14 | /** 15 | * Global constants 16 | */ 17 | public class Constants 18 | { 19 | public static final ObjectMapper JACKSON = new ObjectMapper() 20 | .disable(FAIL_ON_UNKNOWN_PROPERTIES).enable(INDENT_OUTPUT); 21 | 22 | public static final ObjectMapper YML = new ObjectMapper(new YAMLFactory()) 23 | .disable(FAIL_ON_UNKNOWN_PROPERTIES).disable(FAIL_ON_INVALID_SUBTYPE) 24 | .enable(ACCEPT_SINGLE_VALUE_AS_ARRAY).enable(UNWRAP_SINGLE_VALUE_ARRAYS) 25 | .enable(INDENT_OUTPUT); 26 | 27 | public static final File CFG_PATH = new File(System.getProperty("user.home"), ".config/mcpm"); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/SpigotEntry.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm 2 | 3 | import net.sourceforge.argparse4j.inf.ArgumentParserException 4 | import org.bukkit.command.Command 5 | import org.bukkit.command.CommandExecutor 6 | import org.bukkit.command.CommandSender 7 | import org.bukkit.plugin.java.JavaPlugin 8 | import org.hydev.mcpm.client.arguments.ArgsParserFactory 9 | import org.hydev.mcpm.client.interaction.SpigotUserHandler 10 | import org.hydev.mcpm.utils.ColorLogger 11 | 12 | /** 13 | * Entrypoint for the Spigot plugin adapter of our program 14 | */ 15 | class SpigotEntry : JavaPlugin(), CommandExecutor 16 | { 17 | private val parser = ArgsParserFactory.serverArgsParser() 18 | private val interaction = SpigotUserHandler() 19 | 20 | companion object 21 | { 22 | private lateinit var instance: SpigotEntry 23 | 24 | @JvmStatic 25 | fun instance() = instance 26 | } 27 | 28 | /** 29 | * onEnable() is called when our plugin is loaded on a Spigot server. 30 | */ 31 | override fun onEnable() 32 | { 33 | // Initialize instance. This is needed for the PluginLoader. 34 | instance = this 35 | 36 | // Initialize logger 37 | logger.info("Enabled!") 38 | 39 | // Register mcpm command 40 | getCommand("mcpm")!!.setExecutor(this) 41 | } 42 | 43 | /** 44 | * onDisable() is called when our plugin is unloaded on a Spigot server. 45 | */ 46 | override fun onDisable() 47 | { 48 | logger.info("Disabled!") 49 | } 50 | 51 | override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean 52 | { 53 | val log = ColorLogger.toMinecraft(sender) 54 | try 55 | { 56 | parser.parse(args, log) 57 | } 58 | catch (e: ArgumentParserException) 59 | { 60 | parser.fail(e, log) 61 | } 62 | return true 63 | } 64 | 65 | override fun onTabComplete(sender: CommandSender, command: Command, alias: String, args: Array): List? 66 | { 67 | if (command.name.lowercase() != "mcpm") return null 68 | return if (args.size == 1) parser.rawSubparsers.map { it.name() } 69 | else null 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/ArgsParserFactory.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments; 2 | 3 | /** 4 | * Class that produces default implementations of the Controller and ArgsParser classes. 5 | * These classes are used to handle commands. 6 | * Checkout Command.java for a process on adding a new command! 7 | */ 8 | public class ArgsParserFactory { 9 | // No instantiation. 10 | private ArgsParserFactory() { } 11 | 12 | /** 13 | * Creates an ArgsParser object that has general parsers (works in any environment). 14 | * 15 | * @return An ArgsParser object. Invoke ArgsParser#parse to see more. 16 | */ 17 | public static ArgsParser baseArgsParser() { 18 | var interactors = new InteractorFactory(false); 19 | var controllers = new ControllerFactory(interactors); 20 | 21 | return new ArgsParser(ParserFactory.baseParsers(controllers)); 22 | } 23 | 24 | /** 25 | * Creates an ArgsParser object that has all (CLI & Server) parsers. 26 | * 27 | * @return An ArgsParser object. Invoke ArgsParser#parse to see more. 28 | */ 29 | public static ArgsParser serverArgsParser() { 30 | var interactors = new InteractorFactory(true); 31 | var controllers = new ControllerFactory(interactors); 32 | 33 | return new ArgsParser(ParserFactory.serverParsers(controllers)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/HelpException.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments; 2 | 3 | import net.sourceforge.argparse4j.inf.ArgumentParser; 4 | import net.sourceforge.argparse4j.inf.ArgumentParserException; 5 | 6 | /** 7 | * Temporary exception thrown to store a help message 8 | */ 9 | public class HelpException extends ArgumentParserException 10 | { 11 | private final String help; 12 | 13 | public HelpException(ArgumentParser parser, String help) 14 | { 15 | super(parser); 16 | this.help = help; 17 | } 18 | 19 | public String help() 20 | { 21 | return help; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/PrintHelpAction.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments; 2 | 3 | import net.sourceforge.argparse4j.inf.Argument; 4 | import net.sourceforge.argparse4j.inf.ArgumentAction; 5 | import net.sourceforge.argparse4j.inf.ArgumentParser; 6 | import net.sourceforge.argparse4j.inf.Subparser; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Print help argument action to create help messages 12 | */ 13 | public record PrintHelpAction(Subparser subparser) implements ArgumentAction 14 | { 15 | @Override 16 | public void run(ArgumentParser parser, Argument arg, Map attrs, 17 | String flag, Object value) throws HelpException 18 | { 19 | throw new HelpException(parser, subparser.formatHelp()); 20 | } 21 | 22 | @Override 23 | public void onAttach(Argument arg) 24 | { 25 | } 26 | 27 | @Override 28 | public boolean consumeArgument() 29 | { 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/CommandConfigurator.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Subparser; 4 | 5 | /** 6 | * Provides an interface for providing Command details and setting up a Subparser object. 7 | */ 8 | public interface CommandConfigurator { 9 | /** 10 | * Name of the command 11 | * 12 | * @return Name 13 | */ 14 | String name(); 15 | 16 | /** 17 | * Description of the command. If the description is empty, it will not show up in the help menu. 18 | * 19 | * @return Description 20 | */ 21 | String description(); 22 | 23 | /** 24 | * Usually, the body of this command goes as follows: 25 | * 26 | *
27 |      *   parser.addArgument("searchParameter").dest("searchText");
28 |      *   parser.addArgument("searchType").dest("searchType");
29 |      * 
30 | * 31 | * See usage for how to add arguments. 32 | *

33 | * This will ensure that your build method gets called when your subcommand is executed. 34 | * Just do it! 35 | * 36 | * @param parser A subparser 37 | */ 38 | void configure(Subparser parser); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Provides an implementation for a Command. 9 | * Typically, an implementation will involve grabbing view details from the Namespace and calling a controller. 10 | */ 11 | public interface CommandHandler 12 | { 13 | /** 14 | * Called when the user executed the Subparser command in configure. 15 | * The details of what the user entered ("searchText", "searchType") are in the details object. 16 | * We should return a CommandEntry object (e.g. SearchEntry) that contains the parameters that the user expects. 17 | * 18 | *

19 |      *   return new SearchEntry(
20 |      *     details.getString("searchText"),
21 |      *     details.getString("searchType")
22 |      *   );
23 |      * 
24 | * 25 | * @param details A details object that contains all the arguments that the user executed this command with. 26 | */ 27 | void run(Namespace details, Consumer log); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/CommandParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | /** 4 | * Implemented by parsers that define methods to "configure" ArgsParse4j and then "build" a CommandEntry object. 5 | */ 6 | public interface CommandParser extends CommandHandler, CommandConfigurator { } 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/ExportPluginsParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.ExportController; 6 | import org.hydev.mcpm.client.export.ExportPluginsInput; 7 | 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Parser for the ExportPluginsBoundary interface. 12 | */ 13 | public record ExportPluginsParser(ExportController controller) implements CommandParser 14 | { 15 | @Override 16 | public String name() 17 | { 18 | return "export"; 19 | } 20 | 21 | @Override 22 | public String description() 23 | { 24 | return "Export plugin configuration"; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) 29 | { 30 | parser.addArgument("type").nargs("?").choices("pastebin", "file", "literal") 31 | .setDefault("pastebin") // type of output 32 | .type(String.class).dest("type"); 33 | parser.addArgument("out").nargs("?") 34 | .type(String.class).dest("out"); 35 | } 36 | 37 | @Override 38 | public void run(Namespace details, Consumer log) 39 | { 40 | controller.export(new ExportPluginsInput(details.get("type"), details.get("out")), log); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/ImportParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.ImportController; 6 | import org.hydev.mcpm.client.export.ImportInput; 7 | 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Parser for the import use case 12 | */ 13 | public record ImportParser(ImportController controller) implements CommandParser { 14 | @Override 15 | public String name() { 16 | return "import"; 17 | } 18 | 19 | @Override 20 | public String description() { 21 | return "Import a plugins config from a previous export"; 22 | } 23 | 24 | @Override 25 | public void configure(Subparser parser) { 26 | parser.addArgument("type").nargs("?").choices("pastebin", "file", "literal") 27 | .setDefault("pastebin") // type of input 28 | .type(String.class).dest("type"); // of type OutputStream 29 | parser.addArgument("input") 30 | .type(String.class).dest("input"); 31 | } 32 | 33 | @Override 34 | public void run(Namespace details, Consumer log) { 35 | controller.importPlugins(new ImportInput(details.get("type"), details.get("input")), log); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/InfoParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.InfoController; 6 | import org.hydev.mcpm.client.commands.presenters.InfoPresenter; 7 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 8 | 9 | import java.util.function.Consumer; 10 | 11 | /** 12 | * Command parser for the info use case 13 | */ 14 | public record InfoParser(InfoController controller, InfoPresenter presenter) implements CommandParser 15 | { 16 | @Override 17 | public String name() 18 | { 19 | return "info"; 20 | } 21 | 22 | @Override 23 | public String description() 24 | { 25 | return "Show the info of an installed plugin"; 26 | } 27 | 28 | @Override 29 | public void run(Namespace details, Consumer log) 30 | { 31 | var name = details.getString("name"); 32 | try 33 | { 34 | presenter.present(controller.info(name), log); 35 | } 36 | catch (PluginNotFoundException e) 37 | { 38 | log.accept(String.format("&cCannot find plugin '%s'", name)); 39 | } 40 | } 41 | 42 | @Override 43 | public void configure(Subparser parser) 44 | { 45 | parser.addArgument("name") 46 | .help("Name of the plugin"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/InstallParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.impl.Arguments; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | import net.sourceforge.argparse4j.inf.Subparser; 6 | import org.hydev.mcpm.client.commands.controllers.InstallController; 7 | import org.hydev.mcpm.client.commands.presenters.InstallResultPresenter; 8 | import org.hydev.mcpm.client.installer.output.InstallResult; 9 | import org.hydev.mcpm.client.search.SearchPackagesType; 10 | 11 | import java.util.List; 12 | import java.util.function.Consumer; 13 | 14 | /** 15 | * Handles parsing install arguments (to be dispatched to Controller). 16 | */ 17 | 18 | public record InstallParser(InstallController controller, InstallResultPresenter presenter) implements CommandParser 19 | { 20 | @Override 21 | public String name() { 22 | return "install"; 23 | } 24 | 25 | @Override 26 | public String description() { 27 | return "Download and install a plugin from the database"; 28 | } 29 | 30 | @Override 31 | public void configure(Subparser parser) { 32 | parser.addArgument("name").dest("name") 33 | .help("The name of the plugin you want to install"); 34 | parser.addArgument("--no-load").action(Arguments.storeTrue()).dest("noLoad") 35 | .help("Default load, use this option if you don't want to load after install"); 36 | } 37 | 38 | @Override 39 | public void run(Namespace details, Consumer log) { 40 | var name = details.getString("name"); 41 | List result = controller.install(name, 42 | SearchPackagesType.BY_NAME, 43 | !details.getBoolean("noLoad")); 44 | presenter.displayResult(result, log); 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/ListParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.ListController; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Command parser for List command 11 | */ 12 | public record ListParser(ListController controller) implements CommandParser { 13 | @Override 14 | public String name() { 15 | return "list"; 16 | } 17 | 18 | @Override 19 | public String description() { 20 | return "List installed plugins"; 21 | } 22 | 23 | @Override 24 | public void configure(Subparser parser) { 25 | parser.addArgument("type").choices("all", "manual", "automatic", "outdated").setDefault("all").nargs("?"); 26 | } 27 | 28 | @Override 29 | public void run(Namespace details, Consumer log) { 30 | controller.listAll(details.getString("type"), log); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/LoadParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.LoadController; 6 | 7 | import java.util.function.Consumer; 8 | 9 | 10 | /** 11 | * Argument parser for LoadCommand. See LoadEntry. 12 | */ 13 | public record LoadParser(LoadController controller) implements CommandParser 14 | { 15 | @Override 16 | public String name() 17 | { 18 | return "load"; 19 | } 20 | 21 | @Override 22 | public String description() 23 | { 24 | return "Load a plugin in the plugins folder"; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) 29 | { 30 | parser.addArgument("plugins").dest("plugins").nargs("+") 31 | .help("Name of the plugins to load"); 32 | } 33 | 34 | @Override 35 | public void run(Namespace details, Consumer log) 36 | { 37 | controller.load(details.getList("plugins"), log); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/MirrorParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.impl.Arguments; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | import net.sourceforge.argparse4j.inf.Subparser; 6 | import org.hydev.mcpm.client.commands.controllers.MirrorController; 7 | 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Parser for the mirror selector command 12 | * 13 | * @param controller Mirror controller 14 | */ 15 | public record MirrorParser(MirrorController controller) implements CommandParser 16 | { 17 | @Override 18 | public String name() 19 | { 20 | return "mirror"; 21 | } 22 | 23 | @Override 24 | public String description() 25 | { 26 | return "Select a source (mirror) to download plugins from"; 27 | } 28 | 29 | @Override 30 | public void run(Namespace details, Consumer log) 31 | { 32 | var op = details.getString("op"); 33 | switch (op) 34 | { 35 | case "ping" -> controller.ping(details.getBoolean("refresh"), log); 36 | case "select" -> controller.select(details.getString("host"), log); 37 | default -> throw new UnsupportedOperationException("Unknown operation: " + op); 38 | } 39 | } 40 | 41 | @Override 42 | public void configure(Subparser parser) 43 | { 44 | var sub = parser.addSubparsers(); 45 | 46 | var ping = sub.addParser("ping") 47 | .help("Ping mirrors"); 48 | ping.addArgument("-r", "--refresh").action(Arguments.storeTrue()).dest("refresh") 49 | .help("Refresh the mirror list database"); 50 | ping.setDefault("op", "ping"); 51 | 52 | var sel = sub.addParser("select") 53 | .help("Select a mirror"); 54 | sel.addArgument("host").nargs("?") 55 | .help("Host of the mirror"); 56 | sel.setDefault("op", "select"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/PageParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | 6 | import org.hydev.mcpm.client.commands.controllers.PageBoundary; 7 | 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Parser for the pagination command 12 | */ 13 | public record PageParser(PageBoundary controller) implements CommandParser 14 | { 15 | @Override 16 | public String name() 17 | { 18 | return "page"; 19 | } 20 | 21 | @Override 22 | public String description() 23 | { 24 | return ""; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) 29 | { 30 | parser.addArgument("page").type(Integer.class).help("Number of the page you want to view"); 31 | } 32 | 33 | @Override 34 | public void run(Namespace details, Consumer log) 35 | { 36 | var page = controller.formatPage(details.getInt("page")); 37 | if (page == null) 38 | { 39 | log.accept("&cNo pages available."); 40 | return; 41 | } 42 | log.accept(page); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/RefreshParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.RefreshController; 6 | 7 | import java.io.IOException; 8 | import java.util.function.Consumer; 9 | 10 | /** 11 | * Refresh the database cache and mirror list 12 | */ 13 | public record RefreshParser(RefreshController controller) implements CommandParser 14 | { 15 | @Override 16 | public String name() 17 | { 18 | return "refresh"; 19 | } 20 | 21 | @Override 22 | public String description() 23 | { 24 | return "Refresh cached plugin database"; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) 29 | { 30 | 31 | } 32 | 33 | @Override 34 | public void run(Namespace details, Consumer log) 35 | { 36 | try 37 | { 38 | controller.refresh(); 39 | log.accept("&aDatabase refreshed successfully!"); 40 | } 41 | catch (IOException e) 42 | { 43 | log.accept("&cDatabase refresh failed: " + e.getMessage()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/ReloadParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.ReloadController; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Argument parser for ReloadCommand. See ReloadEntry. 11 | */ 12 | public record ReloadParser(ReloadController controller) implements CommandParser 13 | { 14 | @Override 15 | public String description() 16 | { 17 | return "Reload a currently loaded plugin"; 18 | } 19 | 20 | @Override 21 | public String name() 22 | { 23 | return "reload"; 24 | } 25 | 26 | @Override 27 | public void configure(Subparser parser) 28 | { 29 | parser.addArgument("plugins").dest("plugins").nargs("+") 30 | .help("Name of the plugins to reload"); 31 | } 32 | 33 | @Override 34 | public void run(Namespace details, Consumer log) 35 | { 36 | controller.reload(details.get("plugins"), log); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/UninstallParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.impl.Arguments; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | import net.sourceforge.argparse4j.inf.Subparser; 6 | import org.hydev.mcpm.client.commands.controllers.UninstallController; 7 | import org.hydev.mcpm.client.commands.presenters.UninstallResultPresenter; 8 | 9 | import java.util.function.Consumer; 10 | 11 | /** 12 | * Command parser for the uninstallation use case 13 | */ 14 | public record UninstallParser(UninstallController controller, UninstallResultPresenter presenter) 15 | implements CommandParser 16 | { 17 | @Override 18 | public String name() { 19 | return "uninstall"; 20 | } 21 | 22 | @Override 23 | public String description() { 24 | return "Uninstall a plugin from file system"; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) { 29 | parser.addArgument("name") 30 | .help("Name of the plugin to uninstall"); 31 | 32 | parser.addArgument("-n", "--no-recursive") 33 | .action(Arguments.storeFalse()) 34 | .setDefault(true) 35 | .dest("recursive") 36 | .help("Recursively remove orphan dependencies"); 37 | } 38 | 39 | @Override 40 | public void run(Namespace details, Consumer log) 41 | { 42 | var name = details.getString("name"); 43 | 44 | // Uninstall 45 | var result = controller.uninstall(name, details.getBoolean("recursive")); 46 | presenter.displayResult(name, result, log); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/UnloadParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.inf.Namespace; 4 | import net.sourceforge.argparse4j.inf.Subparser; 5 | import org.hydev.mcpm.client.commands.controllers.UnloadController; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Argument parser for UnloadCommand. See UnloadEntry. 11 | */ 12 | public record UnloadParser(UnloadController controller) implements CommandParser 13 | { 14 | @Override 15 | public String name() 16 | { 17 | return "unload"; 18 | } 19 | 20 | @Override 21 | public String description() 22 | { 23 | return "Unload a currently loaded plugin"; 24 | } 25 | 26 | @Override 27 | public void configure(Subparser parser) 28 | { 29 | parser.addArgument("plugins").dest("plugins").nargs("+") 30 | .help("Name of the plugins to unload"); 31 | } 32 | 33 | @Override 34 | public void run(Namespace details, Consumer log) 35 | { 36 | controller.unload(details.getList("plugins"), log); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/arguments/parsers/UpdateParser.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.parsers; 2 | 3 | import net.sourceforge.argparse4j.impl.Arguments; 4 | import net.sourceforge.argparse4j.inf.Namespace; 5 | import net.sourceforge.argparse4j.inf.Subparser; 6 | import org.hydev.mcpm.client.commands.controllers.UpdateController; 7 | import org.hydev.mcpm.client.display.presenters.LogUpdatePresenter; 8 | 9 | import java.util.function.Consumer; 10 | 11 | /** 12 | * Handles parsing related to the update command. 13 | * 14 | * @param controller A controller to dispatch an update command when invoked. 15 | */ 16 | public record UpdateParser(UpdateController controller) implements CommandParser { 17 | @Override 18 | public String name() { 19 | return "update"; 20 | } 21 | 22 | @Override 23 | public String description() { 24 | return "Updates plugins to the latest version."; 25 | } 26 | 27 | @Override 28 | public void configure(Subparser parser) { 29 | // if (Constants.IS_MINECRAFT) { 30 | parser.addArgument("--load") 31 | .type(boolean.class) 32 | .action(Arguments.storeTrue()) 33 | .dest("load") 34 | .help("If true, updated plugins will be reloaded after the update."); 35 | // } 36 | 37 | parser.addArgument("--no-cache") 38 | .type(boolean.class) 39 | .action(Arguments.storeTrue()) 40 | .dest("no-cache") 41 | .help("If true, the cache will be skipped and database will be fetched again."); 42 | 43 | parser.addArgument("names") 44 | .nargs("*") 45 | .help("List of plugin names to update."); 46 | } 47 | 48 | @Override 49 | public void run(Namespace details, Consumer log) { 50 | // Since log can change from invocation to invocation, 51 | // and I don't want UpdatePresenter to depend on Consumer, 52 | // I'll instantiate this every call. 53 | var presenter = new LogUpdatePresenter(log); 54 | controller.update( 55 | details.getList("names"), 56 | details.getBoolean("load"), 57 | details.getBoolean("no-cache"), 58 | presenter 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/ExportController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.ExportPresenter; 4 | import org.hydev.mcpm.client.export.ExportPluginsBoundary; 5 | import org.hydev.mcpm.client.export.ExportPluginsInput; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Controller for the export plugins use case. 11 | * 12 | * @param boundary The export implementation 13 | * @param presenter The presenter to show the result 14 | */ 15 | public record ExportController( 16 | ExportPluginsBoundary boundary, 17 | ExportPresenter presenter 18 | ) { 19 | 20 | /** 21 | * Call the boundary to perform an export. 22 | * 23 | * @param input Input specifying the export parameters 24 | * @param log where to log the operation 25 | */ 26 | public void export(ExportPluginsInput input, Consumer log) { 27 | var result = boundary.export(input); 28 | presenter.present(result, log); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/ImportController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.ImportPresenter; 4 | import org.hydev.mcpm.client.export.ImportInput; 5 | import org.hydev.mcpm.client.export.ImportPluginsBoundary; 6 | 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Controller for the import use case. 11 | * 12 | * @param boundary The boundary used to import 13 | * @param presenter The presenter to display the result 14 | */ 15 | public record ImportController( 16 | ImportPluginsBoundary boundary, 17 | ImportPresenter presenter 18 | ) { 19 | 20 | /** 21 | * Import the plugins. 22 | * 23 | * @param input Specification of where to import from 24 | * @param log The log to write to 25 | */ 26 | public void importPlugins(ImportInput input, Consumer log) { 27 | var result = boundary.importPlugins(input); 28 | presenter.present(result, log); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/InfoController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 4 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 5 | import org.hydev.mcpm.client.models.PluginYml; 6 | 7 | /** 8 | * Controller for the info use case 9 | */ 10 | public record InfoController(PluginTracker tracker) 11 | { 12 | /** 13 | * Run the info command 14 | * 15 | * @param name Name of the plugin 16 | * 17 | */ 18 | public PluginYml info(String name) throws PluginNotFoundException 19 | { 20 | return tracker.listInstalled().stream().filter(it -> it.name().equalsIgnoreCase(name)).findFirst() 21 | .orElseThrow(() -> new PluginNotFoundException(String.format("Cannot find plugin '%s'", name))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/InstallController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.installer.output.InstallResult; 4 | import org.hydev.mcpm.client.search.SearchPackagesType; 5 | import org.hydev.mcpm.client.installer.InstallBoundary; 6 | import org.hydev.mcpm.client.installer.input.InstallInput; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Controller class for the installation use case. 12 | */ 13 | public record InstallController(InstallBoundary boundary) 14 | { 15 | /** 16 | * Install the plugin 17 | * 18 | * @param name Plugin name from repository 19 | * @param type The type of searching package 20 | * @param load Whether to load after installing 21 | * @return Results 22 | */ 23 | public List install(String name, SearchPackagesType type, boolean load) { 24 | var input = new InstallInput(name, type, load, true); 25 | return boundary.installPlugin(input); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/LoadController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.loader.LoadBoundary; 4 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * A command that handles plugin loading operations. See LoadEntry and LoadParser. 11 | */ 12 | public record LoadController(LoadBoundary loader) { 13 | /** 14 | * Load plugins and output status to log. 15 | * 16 | * @param pluginNames A list of all plugin names to be loaded. 17 | * @param log Callback for status for log events. 18 | */ 19 | public void load(List pluginNames, Consumer log) { 20 | for (var name : pluginNames) { 21 | try { 22 | log.accept(String.format("&6Loading %s...", name)); 23 | if (loader.loadPlugin(name)) { 24 | log.accept(String.format("&aPlugin %s loaded", name)); 25 | } else { 26 | log.accept(String.format("&cError: Failed to load plugin %s", name)); 27 | } 28 | } catch (PluginNotFoundException e) { 29 | log.accept(String.format("&cError: Plugin %s not found", name)); 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/PageBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.PagedPresenter; 4 | 5 | import javax.annotation.Nullable; 6 | 7 | /** 8 | * Page controller boundary 9 | */ 10 | public interface PageBoundary 11 | { 12 | /** 13 | * Get a printable formatted page 14 | * 15 | * @param page Page number 16 | * @return Page content 17 | */ 18 | @Nullable 19 | String formatPage(int page); 20 | 21 | /** 22 | * Persist a specific page presenter 23 | * 24 | * @param pager Page presenter 25 | */ 26 | void store(PagedPresenter pager); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/RefreshController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcher; 4 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcherListener; 5 | import org.hydev.mcpm.client.database.mirrors.MirrorSelectBoundary; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Controller for the refresh command 11 | */ 12 | public record RefreshController( 13 | DatabaseFetcher fetcher, 14 | DatabaseFetcherListener listener, 15 | MirrorSelectBoundary mirror 16 | ) 17 | { 18 | /** 19 | * Refresh the database cache and mirror list 20 | */ 21 | public void refresh() throws IOException 22 | { 23 | // Refresh local database 24 | if (fetcher.fetchDatabase(false, listener) == null) 25 | { 26 | throw new IOException("Database fetching failed"); 27 | } 28 | 29 | // Refresh mirror list 30 | mirror.updateMirrors(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/ReloadController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 4 | import org.hydev.mcpm.client.loader.ReloadBoundary; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * A command that handles plugin reloading operations. See ReloadEntry and ReloadParser. 11 | */ 12 | public record ReloadController(ReloadBoundary reloader) { 13 | /** 14 | * Hot reload plugins and output status to log. 15 | * 16 | * @param pluginNames A list of all plugin names to be reloaded. 17 | * @param log Callback for status for log events. 18 | */ 19 | public void reload(List pluginNames, Consumer log) { 20 | for (var name : pluginNames) { 21 | try { 22 | log.accept(String.format("&6Reloading %s...", name)); 23 | reloader.reloadPlugin(name); 24 | log.accept(String.format("&aPlugin %s reloaded!", name)); 25 | } catch (PluginNotFoundException e) { 26 | log.accept(String.format("&cError: Plugin %s not found", name)); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/SearchPackagesController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.search.SearchPackagesBoundary; 4 | import org.hydev.mcpm.client.search.SearchPackagesInput; 5 | import org.hydev.mcpm.client.search.SearchPackagesResult; 6 | import org.hydev.mcpm.client.search.SearchPackagesType; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Handles the user input for a search. 12 | * 13 | */ 14 | public record SearchPackagesController(SearchPackagesBoundary searcher) { 15 | /** 16 | * Load plugins and output status to log. 17 | * 18 | * @param type String that specifies the type of search. 19 | * @param keywords Strings that specifies the search text. 20 | * @param noCache Specifies whether to use local cache or not. 21 | * 22 | * @return The search result. 23 | */ 24 | public SearchPackagesResult searchPackages(String type, List keywords, boolean noCache) { 25 | SearchPackagesInput inp = new SearchPackagesInput( 26 | SearchPackagesType.valueOf("BY_" + type.toUpperCase()), 27 | String.join(" ", keywords), 28 | noCache); 29 | 30 | return searcher.search(inp); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/UninstallController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.uninstall.UninstallBoundary; 4 | import org.hydev.mcpm.client.uninstall.UninstallInput; 5 | import org.hydev.mcpm.client.uninstall.UninstallResult; 6 | 7 | /** 8 | * Controller for the uninstallation use case 9 | */ 10 | public record UninstallController(UninstallBoundary boundary) { 11 | /** 12 | * Uninstall a plugin 13 | * 14 | * @param name Name of the plugin 15 | * @param recursive Whether to remove orphan dependencies 16 | * @return Uninstall result 17 | */ 18 | public UninstallResult uninstall(String name, boolean recursive) { 19 | return boundary.uninstall(new UninstallInput(name, recursive)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/UnloadController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 4 | import org.hydev.mcpm.client.loader.UnloadBoundary; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * A command that handles plugin unloading operations. See UnloadEntry and UnloadParser. 11 | */ 12 | public record UnloadController(UnloadBoundary unloader) { 13 | /** 14 | * Unload plugins and output status to log. 15 | * 16 | * @param pluginNames A list of all plugin names to be unloaded. 17 | * @param log Callback for status for log events. 18 | */ 19 | public void unload(List pluginNames, Consumer log) { 20 | for (var name : pluginNames) { 21 | try { 22 | log.accept(String.format("&6Unloading %s...", name)); 23 | unloader.unloadPlugin(name); 24 | log.accept(String.format("&aPlugin %s unloaded!", name)); 25 | } catch (PluginNotFoundException e) { 26 | log.accept(String.format("&cError: Plugin %s not found", name)); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/controllers/UpdateController.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.controllers; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.UpdatePresenter; 4 | import org.hydev.mcpm.client.updater.UpdateBoundary; 5 | import org.hydev.mcpm.client.updater.UpdateInput; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Controller for the update command. 11 | * 12 | * @param boundary Update requests are dispatched to this boundary. 13 | */ 14 | public record UpdateController(UpdateBoundary boundary) { 15 | /** 16 | * Invoke this to dispatch an update request to the boundary. 17 | * 18 | * @param names A list of names to update. 19 | * @param load Whether to reload installed plugins (ignored on CLI environment). 20 | * @param noCache Whether to force fetch the database before updating. 21 | */ 22 | public void update(List names, boolean load, boolean noCache, UpdatePresenter presenter) { 23 | var input = new UpdateInput(names, load, noCache); 24 | var result = boundary.update(input); 25 | 26 | // This is being done in the Controller for this time being. 27 | // Rena suggested that we move this down into the boundary, I am okay either way. 28 | presenter.present(input, result); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/ExportPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.export.ExportPluginsResult; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Interface for presenting the export use case 9 | */ 10 | public interface ExportPresenter { 11 | void present(ExportPluginsResult exportPluginsResult, Consumer log); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/ImportPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.export.ImportResult; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Interface for displaying the results of an import 9 | */ 10 | public interface ImportPresenter { 11 | 12 | /** 13 | * Present the result of the import 14 | * 15 | * @param result the result to present 16 | */ 17 | void present(ImportResult result, Consumer log); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/InfoPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.models.PluginYml; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Present plugin info command output 9 | */ 10 | public interface InfoPresenter 11 | { 12 | /** 13 | * Present results 14 | */ 15 | void present(PluginYml yml, Consumer log); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/InstallResultPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.installer.output.InstallResult; 4 | 5 | import java.util.List; 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Interface for presenting the state of plugins 10 | * 11 | */ 12 | public interface InstallResultPresenter { 13 | /** 14 | * Display the string to the console 15 | * 16 | * @param results Resulting state of the installation 17 | */ 18 | void displayResult(List results, Consumer log); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/ListResultPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.list.ListResult; 4 | 5 | /** 6 | * Presenter for the list command 7 | */ 8 | public interface ListResultPresenter { 9 | /** 10 | * Display the associated output to the console 11 | * 12 | * @param listResult result of the list command 13 | */ 14 | void displayResult(ListResult listResult); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/PagedPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import com.fasterxml.jackson.annotation.JsonTypeInfo; 4 | 5 | /** 6 | * Presenter for paginated content 7 | */ 8 | @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "type") 9 | public interface PagedPresenter 10 | { 11 | /** 12 | * Format a specific page 13 | * 14 | * @param page Page number (page 1 should be the first page) 15 | * @param lines Lines per page 16 | * @return Formatted page 17 | */ 18 | T presentPage(int page, int lines); 19 | 20 | /** 21 | * Count total pages 22 | * 23 | * @param lines Lines per page 24 | * @return Total amount of pages 25 | */ 26 | int total(int lines); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/SearchResultPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.search.SearchPackagesResult; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Interface for presenting the search result. 9 | * 10 | */ 11 | public interface SearchResultPresenter { 12 | /** 13 | * Display the string to the console. 14 | * 15 | * @param searchResult results of the search. 16 | * @param log log Callback for status for log events. 17 | */ 18 | void displayResult(SearchPackagesResult searchResult, Consumer log); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/UninstallResultPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.uninstall.UninstallResult; 4 | 5 | import java.util.function.Consumer; 6 | 7 | /** 8 | * Interface for presenting uninstall result 9 | */ 10 | public interface UninstallResultPresenter { 11 | /** 12 | * Display uninstall result 13 | * 14 | * @param result Result 15 | * @param log Logger 16 | */ 17 | void displayResult(String name, UninstallResult result, Consumer log); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/commands/presenters/UpdatePresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenters; 2 | 3 | import org.hydev.mcpm.client.updater.UpdateInput; 4 | import org.hydev.mcpm.client.updater.UpdateResult; 5 | 6 | /** 7 | * Interface for presenters to implement who can format update result objects. 8 | */ 9 | public interface UpdatePresenter { 10 | /** 11 | * Formats and displays an UpdateResult object. 12 | * This also takes an input object for a more detailed formatting. 13 | * 14 | * @param input The input object that dispatched the update request. 15 | * @param result The returned result object that contains update outcomes. 16 | */ 17 | void present(UpdateInput input, UpdateResult result); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/fetcher/ConstantFetcher.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.fetcher; 2 | 3 | import org.hydev.mcpm.client.models.Database; 4 | import org.hydev.mcpm.client.models.PluginModel; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Database fetcher that takes in a database 11 | */ 12 | public class ConstantFetcher implements DatabaseFetcher { 13 | private final Database database; 14 | 15 | public ConstantFetcher(Database database) { 16 | this.database = database; 17 | } 18 | 19 | public ConstantFetcher(List plugins) { 20 | this(new Database(plugins)); 21 | } 22 | 23 | @Override 24 | public @Nullable Database fetchDatabase(boolean cache, DatabaseFetcherListener listener) { 25 | return database; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/fetcher/DatabaseFetcher.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.fetcher; 2 | 3 | import org.hydev.mcpm.client.models.Database; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | /** 7 | * Exposes information for a class that can create a Database model for use in searches, etc. 8 | */ 9 | public interface DatabaseFetcher { 10 | /** 11 | * Invoked to construct a database object. 12 | * 13 | * @param cache If false, the underlying fetcher should always skip checking the cache if any exists. 14 | * @param listener Receives updates for database downloading events if the database is fetched from a stream. 15 | * @return The resulting database object. 16 | */ 17 | @Nullable 18 | Database fetchDatabase(boolean cache, DatabaseFetcherListener listener); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/fetcher/DatabaseFetcherListener.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.fetcher; 2 | 3 | /** 4 | * Receives events on database download progress for DatabaseFetcher classes. 5 | */ 6 | public interface DatabaseFetcherListener { 7 | /** 8 | * Called to update the client on download progress when it receives additional bytes. 9 | * In a successful download, this method should be called at least once where completed == total. 10 | * 11 | * @param completed The amount of database bytes received up to this point. 12 | * @param total The amount of total bytes that must be downloaded. 13 | */ 14 | void download(long completed, long total); 15 | 16 | /** 17 | * Called when download has finished or aborted so the client can clean up. 18 | */ 19 | void finish(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/fetcher/QuietFetcherListener.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.fetcher; 2 | 3 | /** 4 | * Stub implementation of DatabaseFetcherListener. 5 | * When the download method is called for the first time, the user will be alerted. 6 | */ 7 | public class QuietFetcherListener implements DatabaseFetcherListener { 8 | @Override 9 | public void download(long completed, long total) { 10 | /* nothing */ 11 | } 12 | 13 | @Override 14 | public void finish() { 15 | /* nothing */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/tracker/MockPluginTracker.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.tracker; 2 | 3 | import org.hydev.mcpm.client.local.LocalPluginTracker; 4 | import org.hydev.mcpm.client.models.PluginModel; 5 | import org.hydev.mcpm.client.models.PluginYml; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * A MockPluginTracker that only stores the yml. 12 | */ 13 | public class MockPluginTracker extends LocalPluginTracker { 14 | private final List ymls; 15 | 16 | /** 17 | * Creates a MockPluginTracker with the given plugins. 18 | * 19 | * @param plugins the given plugins 20 | */ 21 | public MockPluginTracker(List plugins) { 22 | ymls = new ArrayList<>(); 23 | for (var plugin : plugins) { 24 | var version = plugin.getLatestPluginVersion(); 25 | 26 | version.ifPresent(pluginVersion -> ymls.add(pluginVersion.meta())); 27 | } 28 | } 29 | 30 | @Override 31 | public List listInstalled() { 32 | return ymls; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/database/tracker/PluginTracker.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database.tracker; 2 | 3 | import org.hydev.mcpm.client.models.PluginTrackerModel; 4 | import org.hydev.mcpm.client.models.PluginYml; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Extended plugin tracker interface (with methods for specific 10 | * versionId/pluginId). 11 | */ 12 | public interface PluginTracker 13 | { 14 | void addEntry(String name, boolean status, long versionId, long pluginId); 15 | 16 | void removeEntry(String name); 17 | 18 | List listEntries(); 19 | 20 | List listInstalled(); 21 | 22 | Boolean findIfInLockByName(String name); 23 | 24 | List listManuallyInstalled(); 25 | 26 | List listOrphanPlugins(boolean considerSoftDependencies); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/ProgressBarBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display; 2 | 3 | /** 4 | * Terminal progress bar based on Xterm escape codes 5 | */ 6 | @SuppressWarnings("unused") 7 | public interface ProgressBarBoundary extends AutoCloseable { 8 | /** 9 | * Append a progress bar at the end 10 | * 11 | * @param bar Row of the progress bar 12 | * @return bar for fluent access 13 | */ 14 | @SuppressWarnings("UnusedReturnValue") 15 | ProgressRowBoundary appendBar(ProgressRowBoundary bar); 16 | 17 | /** 18 | * Finish a progress bar 19 | * 20 | * @param bar Progress bar 21 | */ 22 | void finishBar(ProgressRowBoundary bar); 23 | 24 | /** 25 | * Finalize and close the progress bar (print the final line) 26 | */ 27 | @Override 28 | void close(); 29 | 30 | /** 31 | * Update the actual progress bar string (write to output). 32 | */ 33 | void update(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/ProgressRowBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display; 2 | 3 | import org.hydev.mcpm.client.display.progress.ProgressBarTheme; 4 | 5 | /** 6 | * Row of a progress bar. 7 | */ 8 | public interface ProgressRowBoundary { 9 | /** 10 | * Get formatted string of the current progress bar 11 | * 12 | * @param theme Progress bar theme 13 | * @param cols Number of columns (width) of the terminal window 14 | * @return Formatted string 15 | */ 16 | String toString(ProgressBarTheme theme, int cols); 17 | 18 | /** 19 | * Update the progress. 20 | * 21 | * @param completed Completed so far 22 | */ 23 | void set(long completed); 24 | 25 | /** 26 | * Get how much progress this row has currently completed as a fraction out of 1. 27 | * 28 | * @return the completion of this row. 29 | */ 30 | double getCompletion(); 31 | 32 | /** 33 | * Set the current progress bar for this row. 34 | * 35 | * @param bar The bar to set as the row's parent. 36 | */ 37 | void setPb(ProgressBarBoundary bar); 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/ProgressSpeedBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display; 2 | 3 | /** 4 | * Defines an interface for a class that can calculate the speed of a ProgressRowBoundary using the last x seconds. 5 | */ 6 | public interface ProgressSpeedBoundary { 7 | /** 8 | * Get the speed of the ProgressRowBoundary. 9 | * 10 | * @return the speed of the ProgressRowBoundary 11 | */ 12 | double getSpeed(); 13 | 14 | /** 15 | * Whenever the associated ProgressRowBoundary has setProgress called, call this as well. 16 | * 17 | * @param progress the updated progress 18 | */ 19 | void setProgress(long progress); 20 | 21 | /** 22 | * Whenever the associated ProgressRowBoundary has incProgress called, call this as well. 23 | * 24 | * @param inc the increment of the progress 25 | */ 26 | void incProgress(long inc); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/InstallPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.InstallResultPresenter; 4 | import org.hydev.mcpm.client.installer.output.InstallResult; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Implementation to the ResultPresenter, display the result of installation of plugins 11 | */ 12 | public class InstallPresenter implements InstallResultPresenter { 13 | 14 | @Override 15 | public void displayResult(List results, Consumer log) { 16 | for (var r : results) { 17 | log.accept("[" + r.name() + "] " + r.type().reason()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/ListPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.ListResultPresenter; 4 | import org.hydev.mcpm.client.list.ListResult; 5 | 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | import static org.hydev.mcpm.client.display.presenters.Table.tabulate; 10 | 11 | /** 12 | * Presenter for the list result 13 | */ 14 | public class ListPresenter implements ListResultPresenter { 15 | private final Consumer log; 16 | 17 | /** 18 | * Instantiate Install Presenter 19 | * 20 | * @param log log string to the console 21 | */ 22 | 23 | public ListPresenter(Consumer log) { 24 | this.log = log; 25 | } 26 | 27 | @Override 28 | public void displayResult(ListResult listResult) { 29 | var list = listResult.queryResult(); 30 | if (listResult.type() == ListResult.Type.SEARCH_INVALID_INPUT) { 31 | log.accept(listResult.type().reason() + "\n" + "Invalid input. Please enter one of the following: " + 32 | "all, manual, automatic, outdated"); 33 | } else if (listResult.type() == ListResult.Type.SEARCH_FAILED_TO_FETCH_INSTALLED) { 34 | log.accept(listResult.type().reason() + "\n" + "Unable to fetch result."); 35 | } else { 36 | var table = tabulate( 37 | list.stream().map(p -> List.of("&a" + p.name(), "&e" + p.getFirstAuthor(), p.version())).toList(), 38 | List.of(":Name", "Author", "Version:")); 39 | 40 | this.log.accept(listResult.type().reason() + " (Parameter " + listResult.listType() + ")\n" + table); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/LogExportPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.ExportPresenter; 4 | import org.hydev.mcpm.client.export.ExportPluginsResult; 5 | 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * ExportPresenter that presents to a log. 10 | */ 11 | public class LogExportPresenter implements ExportPresenter { 12 | 13 | private String getColor(ExportPluginsResult result) { 14 | return switch (result.state()) { 15 | case SUCCESS -> "&a"; 16 | case FAILED -> "&c"; 17 | }; 18 | } 19 | 20 | private String getMessage(ExportPluginsResult result) { 21 | return switch ((result.state())) { 22 | case SUCCESS -> "Exported to " + result.export(); 23 | case FAILED -> result.error(); 24 | }; 25 | } 26 | 27 | 28 | @Override 29 | public void present(ExportPluginsResult exportPluginsResult, Consumer log) { 30 | log.accept(getColor(exportPluginsResult) + getMessage(exportPluginsResult)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/LogImportPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.ImportPresenter; 4 | import org.hydev.mcpm.client.export.ImportResult; 5 | 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * ImportPresenter that writes to a log 10 | */ 11 | public class LogImportPresenter implements ImportPresenter { 12 | 13 | private String getColor(ImportResult result) { 14 | return switch (result.state()) { 15 | case SUCCESS -> "&a"; 16 | case PARTIAL_SUCCESS -> "&6"; 17 | case FAILURE -> "&c"; 18 | case IMPORT_ERROR -> "&4"; 19 | }; 20 | } 21 | 22 | private String getMessage(ImportResult result) { 23 | return switch (result.state()) { 24 | case SUCCESS -> "All plugins installed"; 25 | case PARTIAL_SUCCESS -> "Some plugins failed to install:\n" 26 | + String.join("\n", result.nonInstalledPlugins); 27 | case FAILURE -> "All plugins failed to install"; 28 | case IMPORT_ERROR -> "Import failed. " + result.error; 29 | }; 30 | } 31 | 32 | @Override 33 | public void present(ImportResult result, Consumer log) { 34 | log.accept(getColor(result) + getMessage(result)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/SearchPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.controllers.PageBoundary; 4 | import org.hydev.mcpm.client.commands.presenters.SearchResultPresenter; 5 | import org.hydev.mcpm.client.models.PluginModel; 6 | import org.hydev.mcpm.client.search.SearchPackagesResult; 7 | 8 | import javax.annotation.Nullable; 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.function.Consumer; 12 | 13 | /** 14 | * Implementation to the SearchResultPresenter interface, displaying the result of the search command. 15 | */ 16 | public class SearchPresenter implements SearchResultPresenter { 17 | @Nullable 18 | private final PageBoundary pageController; 19 | 20 | /** 21 | * Instantiate Install Presenter 22 | * 23 | * @param pageController controller for pagination. 24 | */ 25 | 26 | public SearchPresenter(@org.jetbrains.annotations.Nullable PageBoundary pageController) { 27 | this.pageController = pageController; 28 | } 29 | 30 | @Override 31 | public void displayResult(SearchPackagesResult result, Consumer log) { 32 | log.accept("Search State: " + result.state().toString()); 33 | 34 | // Print the plugins found 35 | var list = result.plugins().stream() 36 | .map(PluginModel::getLatestPluginVersion) 37 | .filter(Optional::isPresent) 38 | .map(it -> it.get().meta()) 39 | .toList(); 40 | 41 | var table = new Table(List.of(":Name", "Author", "Version:"), 42 | list.stream().map(p -> List.of("&a" + p.name(), "&e" + p.getFirstAuthor(), p.version())).toList()); 43 | 44 | // Pagination 45 | if (pageController != null) { 46 | pageController.store(table); 47 | log.accept(pageController.formatPage(1)); 48 | } 49 | else { 50 | log.accept(table.toString()); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/presenters/UninstallPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.presenters; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.UninstallResultPresenter; 4 | import org.hydev.mcpm.client.uninstall.UninstallResult; 5 | 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * Implementation of UninstallResultPresenter 10 | */ 11 | public class UninstallPresenter implements UninstallResultPresenter { 12 | /** 13 | * Display one result 14 | * 15 | * @param name Name of the plugin 16 | * @param state State 17 | * @param log Logger object 18 | */ 19 | private void display(String name, UninstallResult.State state, Consumer log) { 20 | var prefix = "[" + name + "] "; 21 | switch (state) { 22 | case NOT_FOUND -> log.accept(prefix + "&cPlugin not found"); 23 | case FAILED_TO_DELETE -> log.accept(prefix + "&cFailed to delete plugin file"); 24 | case SUCCESS -> log.accept(prefix + "&aPlugin uninstalled successfully!"); 25 | default -> log.accept(state.name()); 26 | } 27 | } 28 | 29 | @Override 30 | public void displayResult(String name, UninstallResult result, Consumer log) { 31 | display(name, result.state(), log); 32 | result.dependencies().forEach((depName, depState) -> display(depName, depState, log)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/progress/ProgressBarFetcherListener.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.progress; 2 | 3 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcherListener; 4 | import org.hydev.mcpm.client.display.ProgressBarBoundary; 5 | import org.hydev.mcpm.client.display.ProgressRowBoundary; 6 | 7 | /** 8 | * Handles database download events by forwarding them to a ProgressBar instance. 9 | */ 10 | public class ProgressBarFetcherListener implements DatabaseFetcherListener { 11 | private ProgressBarBoundary cachedBar; 12 | private ProgressRowBoundary cachedRow; 13 | 14 | private final ProgressBarTheme theme; 15 | 16 | /** 17 | * Create a ProgressBar with a default ASCII_THEME. 18 | */ 19 | public ProgressBarFetcherListener() { 20 | this(ProgressBarTheme.ASCII_THEME); 21 | } 22 | 23 | /** 24 | * Create a ProgressBar with the provided them. 25 | * 26 | * @param theme The theme of the underlying ProgressBar. 27 | */ 28 | public ProgressBarFetcherListener(ProgressBarTheme theme) { 29 | this.theme = theme; 30 | } 31 | 32 | // Writes to cachedBar/cachedRow... 33 | private void createBar(long total) { 34 | var bar = new ProgressBar(theme); 35 | var row = new ProgressRow(total) 36 | .desc("Database") 37 | .descLen(20); 38 | 39 | bar.appendBar(row); 40 | 41 | cachedBar = bar; 42 | cachedRow = row; 43 | } 44 | 45 | private ProgressRowBoundary getRow(long total) { 46 | if (cachedRow == null) { 47 | createBar(total); 48 | } 49 | 50 | return cachedRow; 51 | } 52 | 53 | @Override 54 | public void download(long completed, long total) { 55 | var row = getRow(total); 56 | 57 | row.set(completed); 58 | } 59 | 60 | @Override 61 | public void finish() { 62 | if (cachedBar != null && cachedRow != null) { 63 | cachedBar.finishBar(cachedRow); 64 | cachedBar.close(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/display/progress/ProgressBarTheme.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.display.progress; 2 | 3 | /** 4 | * Theme for the progress bar 5 | * 6 | * @param ipr The character displayed for in-progress (e.g. " ") 7 | * @param done The character displayed for done (e.g. "#") 8 | * @param iprLen Displayed length of the ipr string (Some characters have length 1 but take up two characters' space) 9 | * @param doneLen Displayed length of the done string 10 | */ 11 | @SuppressWarnings("unused") 12 | public record ProgressBarTheme( 13 | String done, 14 | String ipr, 15 | String prefix, 16 | String suffix, 17 | int doneLen, 18 | int iprLen 19 | ) 20 | { 21 | public static final ProgressBarTheme ASCII_THEME = new ProgressBarTheme("#", "-", "[", "]", 1, 1); 22 | public static final ProgressBarTheme CLASSIC_THEME = new ProgressBarTheme("█", ".", "", "", 1, 1); 23 | public static final ProgressBarTheme EMOJI_THEME = new ProgressBarTheme("✅", "🕑", "", "", 2, 2); 24 | public static final ProgressBarTheme FLOWER_THEME = new ProgressBarTheme("🌸", "🥀", "", "", 2, 2); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ExportInteractor.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 5 | import org.hydev.mcpm.client.export.storage.StringStorage; 6 | import org.hydev.mcpm.client.export.storage.StringStorageFactory; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.hydev.mcpm.Constants.JACKSON; 11 | 12 | /** 13 | * An implementation of ExportPluginsBoundary that fetches from a database. 14 | */ 15 | public record ExportInteractor(PluginTracker tracker) implements ExportPluginsBoundary { 16 | 17 | /** 18 | * Outputs the plugins on each line as its name and version separated by a space. 19 | * 20 | * @param input The output stream to write to 21 | * @return whether the export was successful or otherwise. 22 | */ 23 | @Override 24 | public ExportPluginsResult export(ExportPluginsInput input) { 25 | var plugins = tracker.listInstalled(); 26 | if (plugins == null) { 27 | return new ExportPluginsResult(ExportPluginsResult.State.FAILED, null, 28 | "Could not fetch plugins"); 29 | } 30 | var models = plugins.stream().map(p -> new ExportModel(p.name(), p.version())).toList(); 31 | 32 | try { 33 | var answer = JACKSON.writeValueAsString(models); 34 | StringStorage storage = StringStorageFactory.createStringStorage(input); 35 | var token = storage.store(answer); 36 | return new ExportPluginsResult(ExportPluginsResult.State.SUCCESS, token, null); 37 | } catch (JsonProcessingException e) { 38 | // Should never happen 39 | throw new RuntimeException(e); 40 | } catch (IOException e) { 41 | return new ExportPluginsResult(ExportPluginsResult.State.FAILED, null, e.getMessage()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ExportModel.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | /** 4 | * Record storing information to export a plugin 5 | * 6 | * @param name Name of the plugin 7 | * @param version Version of the plugin 8 | */ 9 | public record ExportModel( 10 | String name, 11 | String version 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ExportPluginsBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | /** 4 | * Defines how a user can export their plugins. 5 | */ 6 | public interface ExportPluginsBoundary { 7 | /** 8 | * 9 | * @param input The output stream to write to 10 | * @return Whether the export was successful or not 11 | */ 12 | ExportPluginsResult export(ExportPluginsInput input); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ExportPluginsInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | /** 4 | * Input for the ExportPluginsBoundary boundary. 5 | * 6 | * @param type The type of export 7 | * @param out String describing what to write to 8 | */ 9 | public record ExportPluginsInput( 10 | String type, 11 | String out 12 | ) { 13 | } -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ExportPluginsResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | /** 6 | * Results returned from ExportPluginBoundary. 7 | * 8 | * @param state The outcome of the export. Must be SUCCESS for other values to be valid. 9 | * @param export A string representing where the export went to. 10 | */ 11 | public record ExportPluginsResult( 12 | State state, 13 | @Nullable String export, 14 | @Nullable String error 15 | ) { 16 | /** 17 | * Outcome of the plugin export 18 | */ 19 | public enum State { 20 | SUCCESS, 21 | FAILED 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ImportInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | /** 4 | * The input for the import use case. 5 | * 6 | * @param type The type of the import source (pastebin, file, etc) 7 | * @param input The location of the input 8 | */ 9 | public record ImportInput( 10 | String type, 11 | String input 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ImportInteractor.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.type.TypeReference; 5 | import org.hydev.mcpm.client.export.storage.StringStorage; 6 | import org.hydev.mcpm.client.export.storage.StringStorageFactory; 7 | import org.hydev.mcpm.client.installer.InstallBoundary; 8 | import org.hydev.mcpm.client.installer.input.InstallInput; 9 | import org.hydev.mcpm.client.installer.output.InstallResult; 10 | import org.hydev.mcpm.client.search.SearchPackagesType; 11 | 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import static org.hydev.mcpm.Constants.JACKSON; 17 | 18 | /** 19 | * Default implementation of ImportPluginsBoundary 20 | * 21 | * @param install the installer boundary 22 | */ 23 | public record ImportInteractor(InstallBoundary install) implements ImportPluginsBoundary { 24 | @Override 25 | public ImportResult importPlugins(ImportInput input) { 26 | try { 27 | StringStorage storage = StringStorageFactory.createStringStorage(input); 28 | String json = storage.load(input.input()); 29 | var plugins = JACKSON.readValue(json, new TypeReference>() { }); 30 | // TODO: Install specific versions 31 | var results = new ArrayList(); 32 | for (var p : plugins) { 33 | results.addAll(install.installPlugin( 34 | new InstallInput(p.name(), SearchPackagesType.BY_NAME, true, true))); 35 | } 36 | 37 | return new ImportResult(results); 38 | } catch (JsonProcessingException e) { 39 | return new ImportResult("Import source is invalid or corrupted."); 40 | } catch (IOException e) { 41 | return new ImportResult("Unable to read from import source. " + 42 | e.getMessage()); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/ImportPluginsBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | /** 4 | * Defines how a user can export their plugins. 5 | */ 6 | public interface ImportPluginsBoundary { 7 | /** 8 | * Install and load the plugins 9 | * 10 | * @param input Specification of where to import from 11 | * @return Whether the export was successful or not 12 | */ 13 | ImportResult importPlugins(ImportInput input); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/storage/FixedFileStorage.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export.storage; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | 9 | /** 10 | * Fixed local file string storage. This stores the string to a specific file. 11 | */ 12 | public record FixedFileStorage(Path path) implements StringStorage 13 | { 14 | public FixedFileStorage(String path) { 15 | this(new File(path).toPath()); 16 | } 17 | 18 | 19 | @Override 20 | public String store(String content) throws IOException 21 | { 22 | // Write file 23 | Files.writeString(path, content, StandardCharsets.UTF_8); 24 | return path.toAbsolutePath().toString(); 25 | } 26 | 27 | @Override 28 | public String load(String token) throws IOException 29 | { 30 | // Read file 31 | return Files.readString(path, StandardCharsets.UTF_8); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/storage/PasteBinStorage.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export.storage; 2 | 3 | import org.apache.hc.client5.http.fluent.Request; 4 | import org.apache.hc.core5.http.ContentType; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Store string content to a pastebin that supports raw text requests (e.g. w4bin) 10 | * 11 | * @param host Pastebin host URL 12 | */ 13 | public record PasteBinStorage(String host) implements StringStorage 14 | { 15 | /** 16 | * Construct with default host 17 | */ 18 | public PasteBinStorage() 19 | { 20 | this("https://mcpm.hydev.org/paste/"); 21 | } 22 | 23 | @Override 24 | public String store(String content) throws IOException 25 | { 26 | // Send request 27 | return Request.put(host) 28 | .bodyString(content, ContentType.TEXT_PLAIN) 29 | .execute().returnContent().asString(); 30 | } 31 | 32 | @Override 33 | public String load(String token) throws IOException 34 | { 35 | // If token is not a URL, treat it as the pastebin token and append host from it 36 | if (!token.startsWith("http://") && !token.startsWith("https://")) 37 | { 38 | token = host + token; 39 | } 40 | 41 | // Send request 42 | return Request.get(token) 43 | .addHeader("content-type", "text/plain") 44 | .execute().returnContent().asString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/storage/StringLiteralStorage.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export.storage; 2 | 3 | /** 4 | * A StringStorage where the token is literally the content itself. 5 | */ 6 | public class StringLiteralStorage implements StringStorage { 7 | @Override 8 | public String store(String content) { 9 | return content; 10 | } 11 | 12 | @Override 13 | public String load(String token) { 14 | return token; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/storage/StringStorage.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export.storage; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Basic string content storage 7 | */ 8 | public interface StringStorage 9 | { 10 | /** 11 | * Store content 12 | * 13 | * @param content String content 14 | * @return Token for retrieval 15 | */ 16 | String store(String content) throws IOException; 17 | 18 | /** 19 | * Load content 20 | * 21 | * @param token Token for retrieval 22 | * @return String content 23 | */ 24 | String load(String token) throws IOException; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/export/storage/StringStorageFactory.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export.storage; 2 | 3 | import org.hydev.mcpm.client.export.ExportPluginsInput; 4 | import org.hydev.mcpm.client.export.ImportInput; 5 | 6 | 7 | /** 8 | * Factory for creating StringStorage instances for ExportInteractor 9 | */ 10 | public class StringStorageFactory { 11 | /** 12 | * Create a StringStorage based on the export type. 13 | * 14 | * @param input The export query 15 | * @return a StringStorage satisfying the query 16 | */ 17 | public static StringStorage createStringStorage(ExportPluginsInput input) { 18 | return switch (input.type()) { 19 | case "file" -> input.out() == null ? 20 | new FixedFileStorage("export.json") : new FixedFileStorage(input.out()); 21 | 22 | case "pastebin" -> input.out() == null ? new PasteBinStorage() : new PasteBinStorage(input.out()); 23 | case "literal" -> new StringLiteralStorage(); 24 | default -> throw new IllegalArgumentException("Invalid output type"); 25 | }; 26 | } 27 | 28 | /** 29 | * Create a StringStorage based on the input type. 30 | * 31 | * @param input The input query 32 | * @return a StringStorage satisfying the query 33 | */ 34 | public static StringStorage createStringStorage(ImportInput input) { 35 | return switch (input.type()) { 36 | case "file" -> input.input() == null ? 37 | new FixedFileStorage("export.json") : new FixedFileStorage(input.input()); 38 | 39 | case "pastebin" -> input.input() == null ? new PasteBinStorage() : new PasteBinStorage(input.input()); 40 | case "literal" -> new StringLiteralStorage(); 41 | default -> throw new IllegalArgumentException("Invalid input type"); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/installer/InstallBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer; 2 | 3 | 4 | import org.hydev.mcpm.client.installer.input.InstallInput; 5 | import org.hydev.mcpm.client.installer.output.InstallResult; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Interface for installing plugin to the jar file. 11 | */ 12 | 13 | public interface InstallBoundary { 14 | 15 | /** 16 | * Install a plugin 17 | * 18 | * @param installInput Options 19 | */ 20 | List installPlugin(InstallInput installInput); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/installer/PluginDownloader.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer; 2 | 3 | /** 4 | * Downloader for Spigot plugins 5 | */ 6 | public interface PluginDownloader { 7 | /** 8 | * Download a plugin 9 | * 10 | * @param pluginName The name of the plugin. 11 | * @param pluginId Spigot Plugin ID 12 | * @param pluginVersion Spigot Plugin Version ID 13 | */ 14 | void download(String pluginName, long pluginId, long pluginVersion); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/installer/SpigotPluginDownloader.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer; 2 | 3 | import org.hydev.mcpm.client.Downloader; 4 | 5 | import java.io.IOException; 6 | import java.net.URI; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.function.Supplier; 10 | 11 | import static org.hydev.mcpm.utils.GeneralUtils.concatUri; 12 | 13 | /** 14 | * Plugin downloader for the MCPM Plugin Repository 15 | */ 16 | 17 | public class SpigotPluginDownloader implements PluginDownloader { 18 | private final Downloader downloader; 19 | private final Supplier host; 20 | 21 | /** 22 | * Initialize the Spigot Plugin Downloader 23 | * 24 | * @param downloader The file downloader 25 | * @param host Base URL provider 26 | * */ 27 | public SpigotPluginDownloader(Downloader downloader, Supplier host) { 28 | this.downloader = downloader; 29 | this.host = host; 30 | } 31 | 32 | /** 33 | * Download the plugin according to its filepath. 34 | * 35 | * @param pluginName The name of the plugin. 36 | * @param pluginId The id of the plugin to download. 37 | * @param pluginVersion The associated plugin version. 38 | */ 39 | @Override 40 | public void download(String pluginName, long pluginId, long pluginVersion) { 41 | String filePath = "plugins/" + pluginName + ".jar"; 42 | 43 | try { 44 | // I'm sorry, this is an important for the update API. 45 | Files.createDirectories(Paths.get(filePath).getParent()); 46 | } catch (IOException e) { 47 | /* ignore */ 48 | } 49 | 50 | String url = constructUrl(pluginId, pluginVersion); 51 | downloader.downloadFile(url, filePath); 52 | } 53 | 54 | /** 55 | * Construct Url as a plugin source for installation 56 | * 57 | * @param pluginId Spigot Plugin ID 58 | * @param pluginVersion Spigot Plugin Version ID 59 | */ 60 | private String constructUrl(long pluginId, long pluginVersion) { 61 | var path = String.format("pkgs/spiget/%s/%s/release.jar", pluginId, pluginVersion); 62 | return concatUri(host.get(), path).toString(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/installer/input/InstallInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer.input; 2 | 3 | import org.hydev.mcpm.client.search.SearchPackagesType; 4 | 5 | /** 6 | * Install Plugin Input 7 | * 8 | * @param name Plugin name from repository 9 | * @param type Search packages type (BY_NAME, BY_KEYWORD, BY_COMMAND) 10 | * @param load Whether to load after installing 11 | * @param isManuallyInstalled Whether the user already asked installing the plugin 12 | */ 13 | public record InstallInput(String name, 14 | SearchPackagesType type, 15 | boolean load, 16 | boolean isManuallyInstalled) { 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/installer/output/InstallResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer.output; 2 | 3 | /** 4 | * Exception during installation of a plugin 5 | * 6 | * @param type Status of install result 7 | * @param name Name of the plugin 8 | * @param loaded Whether the installed plugin is successfully loaded 9 | * @since 2022-11-20 10 | */ 11 | public record InstallResult(Type type, String name, boolean loaded) { 12 | 13 | /** 14 | * Default constructor with loaded=false 15 | * 16 | * @param type Status of install result 17 | * @param name Name of the plugin 18 | */ 19 | public InstallResult(Type type, String name) { 20 | this(type, name, false); 21 | } 22 | 23 | /** 24 | * Status of install result 25 | */ 26 | public enum Type { 27 | NOT_FOUND(" &c The plugin by that identifier is not found"), 28 | SEARCH_INVALID_INPUT(" &cInvalid search input"), 29 | SEARCH_FAILED_TO_FETCH_DATABASE(" &cFailed to fetch the MCPM database"), 30 | NO_VERSION_AVAILABLE(" &cNo versions are available to download"), 31 | PLUGIN_EXISTS(" &6The plugin is already installed on the system"), 32 | 33 | SUCCESS_INSTALLED(" &aThe plugin is installed successfully"); 34 | private final String reason; 35 | 36 | Type(String reason) { 37 | this.reason = reason; 38 | } 39 | 40 | public String reason() { 41 | return reason; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/interaction/IUserInteractor.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.interaction 2 | 3 | /** 4 | * An interface to interact with the user. 5 | * 6 | * @author Azalea (https://github.com/hykilpikonna) 7 | * @since 2023-01-02 8 | */ 9 | interface IUserInteractor 10 | { 11 | /** 12 | * Ask the user for an input (non-blocking with coroutine) 13 | * 14 | * @return Text of the user's input 15 | */ 16 | suspend fun input(): String? 17 | 18 | /** 19 | * Output something (Colored) 20 | * 21 | * @param txt: Text to be printed 22 | */ 23 | fun print(txt: String) 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/interaction/SpigotUserHandler.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.interaction 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.EventPriority 6 | import org.bukkit.event.player.AsyncPlayerChatEvent 7 | import org.bukkit.event.player.PlayerQuitEvent 8 | import java.util.UUID 9 | import kotlin.coroutines.resume 10 | import kotlin.coroutines.suspendCoroutine 11 | 12 | /** 13 | * Take in user input through Spigot 14 | * 15 | * @author Azalea (https://github.com/hykilpikonna) 16 | * @since 2023-01-02 17 | */ 18 | class SpigotUserHandler 19 | { 20 | // Listening[uuid] = handler 21 | private val listening: HashMap Unit> = HashMap() 22 | 23 | /** 24 | * When player say something, if we're listening, we hijack the event. 25 | */ 26 | @EventHandler(priority = EventPriority.HIGHEST) 27 | fun onSay(e: AsyncPlayerChatEvent) = listening[e.player.uniqueId]?.let { 28 | // Run callback 29 | it(e.message) 30 | 31 | // Prevent further event handlers from processing 32 | e.isCancelled = true 33 | } 34 | 35 | /** 36 | * When player quit, we stop listening to them. 37 | */ 38 | @EventHandler 39 | fun onQuit(e: PlayerQuitEvent) = listening.remove(e.player.uniqueId) 40 | 41 | /** 42 | * Create a user interactor for a player 43 | */ 44 | fun create(p: Player) = object : IUserInteractor 45 | { 46 | override suspend fun input() = suspendCoroutine { async -> listening[p.uniqueId] = { async.resume(it) } } 47 | 48 | override fun print(txt: String) = p.sendMessage(txt.replace("&", "§")) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/interaction/StdInteractor.kt: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.interaction 2 | 3 | import org.hydev.mcpm.utils.ColorLogger 4 | import org.hydev.mcpm.utils.ColorLogger.printc 5 | 6 | /** 7 | * Take input and give output using STD IO 8 | * 9 | * @author Azalea (https://github.com/hykilpikonna) 10 | * @since 2023-01-02 11 | */ 12 | class StdInteractor : IUserInteractor 13 | { 14 | override suspend fun input(): String? 15 | { 16 | // Since in the CLI mode, there are only ever going to have one user, so blocking is fine 17 | return readlnOrNull() 18 | } 19 | 20 | override fun print(txt: String) 21 | { 22 | printc(txt) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/list/ListAllBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.list; 2 | 3 | import org.hydev.mcpm.client.models.PluginYml; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Interface for listing all plugins 9 | */ 10 | public interface ListAllBoundary { 11 | 12 | /** 13 | * listAllInteractor interacts with the LocalPluginTracker to get the list of 14 | * plugins, according to a specified 15 | * parameter 16 | * 17 | * @param parameter The parameter for the ListAll use case. 'All' denotes a 18 | * request to list all manually 19 | * installed plugins, 'manual' denotes a request to list all 20 | * manually installed plugins, and 'outdated' denotes 21 | * a request to list all manually installed plugins that are 22 | * outdated. 23 | */ 24 | 25 | List listAll(ListType parameter); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/list/ListAllInteractor.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.list; 2 | 3 | import org.hydev.mcpm.client.models.PluginYml; 4 | import org.hydev.mcpm.client.updater.CheckForUpdatesBoundary; 5 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Implementation for the ListAll functionality 11 | */ 12 | public record ListAllInteractor(PluginTracker pluginTracker, CheckForUpdatesBoundary checkForUpdatesBoundary) 13 | implements ListAllBoundary { 14 | /** 15 | * listAllInteractor interacts with the LocalPluginTracker to get the list of 16 | * plugins, according to a specified 17 | * parameter 18 | * 19 | * @param parameter The parameter for the ListAll use case. 'All' denotes a 20 | * request to list all manually 21 | * installed plugins, 'manual' denotes a request to list all 22 | * manually installed plugins, and 'outdated' denotes 23 | * a request to list all manually installed plugins that are 24 | * outdated. 25 | */ 26 | public List listAll(ListType parameter) { 27 | var installed = pluginTracker.listInstalled(); 28 | switch (parameter) { 29 | case ALL -> { 30 | return installed; 31 | } 32 | case MANUAL -> { 33 | var local = pluginTracker.listManuallyInstalled(); 34 | return installed.stream().filter(it -> local.contains(it.name())).toList(); 35 | } 36 | case AUTOMATIC -> { 37 | var manual = pluginTracker.listManuallyInstalled(); 38 | return installed.stream().filter(it -> !manual.contains(it.name())).toList(); 39 | } 40 | case OUTDATED -> { 41 | ListUpdatableBoundary listUpdatableBoundary = new ListUpdatableHelper(); 42 | var outdated = listUpdatableBoundary.listUpdatable(pluginTracker, checkForUpdatesBoundary); 43 | return installed.stream().filter(it -> outdated.contains(it.name())).toList(); 44 | } 45 | default -> { 46 | return null; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/list/ListResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.list; 2 | 3 | 4 | import org.hydev.mcpm.client.models.PluginYml; 5 | 6 | import java.util.List; 7 | 8 | 9 | /** 10 | * Exception during installation of a plugin 11 | */ 12 | public record ListResult(List queryResult, Type type, ListType listType) { 13 | /** 14 | * Type of list failure 15 | */ 16 | public enum Type { 17 | SEARCH_INVALID_INPUT("Invalid search input"), 18 | SEARCH_FAILED_TO_FETCH_INSTALLED("Failed to fetch local files."), 19 | SUCCESS_RETRIEVING_LOCAL_AND_UPDATABLE("Successfully retrieved local files."), 20 | SUCCESS_RETRIEVING_BUT_NO_MATCHES("Successfully retrieved local files, but no matches found."); 21 | 22 | private final String reason; 23 | 24 | Type(String reason) { 25 | this.reason = reason; 26 | } 27 | 28 | public String reason() { 29 | return reason; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/list/ListType.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.list; 2 | 3 | /** 4 | * Enum representing different possible inputs 5 | */ 6 | public enum ListType { 7 | ALL, MANUAL, AUTOMATIC, OUTDATED 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/list/ListUpdatableBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.list; 2 | 3 | import java.util.ArrayList; 4 | import org.hydev.mcpm.client.updater.CheckForUpdatesBoundary; 5 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 6 | 7 | /** 8 | * Defines an interface for obtaining a list of plugin names that may be updated 9 | */ 10 | public interface ListUpdatableBoundary { 11 | ArrayList listUpdatable(PluginTracker superPluginTracker, 12 | CheckForUpdatesBoundary checkForUpdatesBoundary); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/LoadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | /** 4 | * Interface for loading a locally installed plugin. 5 | */ 6 | public interface LoadBoundary 7 | { 8 | /** 9 | * Dynamically load a local plugin through JVM reflections and classloader hacks 10 | * 11 | * @param name Loaded plugin name 12 | * @return True if success, false if failed 13 | * @throws PluginNotFoundException Plugin of the name is not found in the plugins directory 14 | */ 15 | boolean loadPlugin(String name) throws PluginNotFoundException; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/LocalJarBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Boundary for finding local jar files 7 | */ 8 | public interface LocalJarBoundary 9 | { 10 | /** 11 | * Find a jar file of a plugin in file system by name 12 | * 13 | * @param name Plugin name in meta 14 | * @return Plugin jar file 15 | */ 16 | File findJar(String name) throws PluginNotFoundException; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/LocalJarFinder.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | import org.hydev.mcpm.client.models.PluginYml; 4 | import org.hydev.mcpm.utils.PluginJarFile; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.util.Arrays; 9 | import java.util.Optional; 10 | 11 | /** 12 | * Implementation that finds locally installed jars by name 13 | * 14 | * @param dir Plugins directory (default to "./plugins") 15 | */ 16 | public record LocalJarFinder(File dir) implements LocalJarBoundary 17 | { 18 | public LocalJarFinder() 19 | { 20 | this(new File("plugins")); 21 | } 22 | 23 | @Override 24 | public File findJar(String name) throws PluginNotFoundException 25 | { 26 | // 1. Find plugin file by name 27 | if (!dir.isDirectory()) throw new PluginNotFoundException(name); 28 | return Arrays.stream(Optional.ofNullable(dir.listFiles()).orElseThrow(() -> new PluginNotFoundException(name))) 29 | .filter(f -> f.getName().endsWith(".jar")) 30 | .parallel() 31 | .filter(f -> { 32 | try (var jf = new PluginJarFile(f)) 33 | { 34 | return jf.readPluginYaml().name().equalsIgnoreCase(name); 35 | } 36 | catch (IOException | PluginYml.InvalidPluginMetaStructure ignored) { return false; } 37 | }).findFirst().orElseThrow(() -> new PluginNotFoundException(name)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/PluginLoaderHelper.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Entry point for plugin helper in order to reload itself 10 | */ 11 | @SuppressWarnings("unused") 12 | public class PluginLoaderHelper extends JavaPlugin 13 | { 14 | /** 15 | * Reload the mcpm plugin through the helper plugin 16 | * 17 | * @param instance Instance of the mcpm plugin 18 | * @param jar Jar file path of the mcpm plugin 19 | */ 20 | public void reloadMcpm(Plugin instance, File jar) 21 | { 22 | System.out.println("Unloading MCPM..."); 23 | PluginLoader.unloadPlugin(instance); 24 | System.out.println("Loading MCPM..."); 25 | PluginLoader.loadPluginHelper(jar); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/PluginNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | /** 4 | * Not found exception 5 | */ 6 | public class PluginNotFoundException extends Exception 7 | { 8 | public PluginNotFoundException(String msg) 9 | { 10 | super(msg); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/ReloadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | /** 4 | * Interface for reload a locally installed plugin. 5 | */ 6 | public interface ReloadBoundary 7 | { 8 | /** 9 | * Unload then load a plugin 10 | * 11 | * @param name Loaded plugin name 12 | */ 13 | void reloadPlugin(String name) throws PluginNotFoundException; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/loader/UnloadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.loader; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Interface for unloading a locally installed plugin. 7 | */ 8 | public interface UnloadBoundary 9 | { 10 | /** 11 | * Dynamically unload a local plugin through JVM reflections and classloader hacks 12 | * 13 | * @param name Loaded plugin name 14 | * @throws PluginNotFoundException If a loaded plugin of the name isn't found 15 | */ 16 | File unloadPlugin(String name) throws PluginNotFoundException; 17 | 18 | /** 19 | * Unload a plugin then delete it 20 | * 21 | * @param name Name of the plugin 22 | * @return Whether the plugin is successfully unloaded and deleted 23 | * @throws PluginNotFoundException Plugin is not found 24 | */ 25 | default boolean unloadAndDeletePlugin(String name) throws PluginNotFoundException 26 | { 27 | return unloadPlugin(name).delete(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/local/PluginTrackerError.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.local; 2 | 3 | /** 4 | * Exception class for the local plugin tracker 5 | */ 6 | public class PluginTrackerError extends RuntimeException 7 | { 8 | public PluginTrackerError(Throwable cause) 9 | { 10 | super(cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/matcher/MatchPluginsBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.matcher; 2 | 3 | /** 4 | * Defines how a user can match plugins by an identifier. 5 | * This is planned to be used to fetch details about PluginModels 6 | * from the database when we have limited information 7 | * (ex. PluginLock only stores Plugin "names" for some reason). 8 | * This can also be used by CheckForUpdatesBoundary. 9 | */ 10 | public interface MatchPluginsBoundary { 11 | /** 12 | * Looks through the database for plugins that match the model id's in input. 13 | * 14 | * @param input A list of plugin identifiers that will be searched for in the database. 15 | * @return A map of all identifiers to the underlying PluginModel objects (if found). 16 | */ 17 | MatchPluginsResult match(MatchPluginsInput input); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/matcher/MatchPluginsInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.matcher; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Input for the MatchPluginsInput. 7 | * 8 | * @param pluginIds A list of all plugins that should be considered for updates. 9 | * @param noCache If true, the ListPackagesBoundary will re-download the database before performing the request. 10 | */ 11 | public record MatchPluginsInput( 12 | List pluginIds, 13 | boolean noCache 14 | ) { } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/matcher/MatchPluginsResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.matcher; 2 | 3 | import org.hydev.mcpm.client.models.PluginModel; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Result returned from CheckForUpdatesBoundary. 10 | * If a plugin is in the mismatched list, you might want to ask the user if they can be removed. 11 | * 12 | * @param state The outcome of the request. Must be Success for the other values to be valid. 13 | * @param matched A map from version ids to all plugins that match the condition (e.g. needs updates). 14 | * @param mismatched A list of all plugins that were not matched to any Plugin in the database. 15 | */ 16 | public record MatchPluginsResult( 17 | State state, 18 | Map matched, 19 | List mismatched 20 | ) { 21 | /** 22 | * The outcome of the MatchPluginsInput. 23 | */ 24 | public enum State { 25 | SUCCESS, 26 | INVALID_INPUT, 27 | FAILED_TO_FETCH_DATABASE, 28 | } 29 | 30 | /** 31 | * Creates an empty MatchPluginsResult with the provided state. 32 | * The default values are provided in order to easily create objects with failure States. 33 | * 34 | * @param state The state of the MatchPluginsResult. 35 | * @return A MatchPluginsResult object with state initialized and dummy values for the other elements. 36 | */ 37 | public static MatchPluginsResult by(MatchPluginsResult.State state) { 38 | return new MatchPluginsResult(state, Map.of(), List.of()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/matcher/PluginVersionId.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.matcher; 2 | 3 | import org.hydev.mcpm.client.models.PluginVersion; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.util.OptionalLong; 7 | 8 | /** 9 | * Used to identify a plugin version based on many qualifications (id/user string). 10 | * At least one of versionId | versionString must be provided. 11 | * If more than one of these qualifications are provided, then plugins must match all provided entries. 12 | * 13 | * @param versionId The id for the installed PluginVersion. See PluginVersion#id. 14 | * @param versionString The user string for the installed PluginVersion. See PluginYML#version. 15 | */ 16 | public record PluginVersionId( 17 | OptionalLong versionId, 18 | @Nullable String versionString 19 | ) { 20 | /** 21 | * Checks if this object is in a valid state. 22 | * 23 | * @return True if at least one of versionId or versionString is specified. 24 | */ 25 | public boolean valid() { 26 | return versionId.isPresent() || versionString != null; 27 | } 28 | 29 | /** 30 | * Returns true if the provided PluginVersion object matches this PluginVersionId object. 31 | * 32 | * @param version The provided PluginVersion object. 33 | * @return True if the provided versionId or versionString matches the PluginVersion object. 34 | */ 35 | public boolean matches(PluginVersion version) { 36 | return (versionId.isEmpty() || versionId.getAsLong() == version.id()) 37 | && (versionString == null || versionString.equals(version.meta().version())); 38 | } 39 | 40 | /** 41 | * Returns a PluginVersion where the only populated field is versionId. 42 | * 43 | * @param id The value for the id field. 44 | * @return A plugin version id object. 45 | */ 46 | public static PluginVersionId byId(long id) { 47 | return new PluginVersionId(OptionalLong.of(id), null); 48 | } 49 | 50 | /** 51 | * Returns a PluginVersion where the only populated field is versionString. 52 | * 53 | * @param string The value for the string field. 54 | * @return A plugin version id object. 55 | */ 56 | public static PluginVersionId byString(String string) { 57 | return new PluginVersionId(OptionalLong.empty(), string); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/matcher/PluginVersionState.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.matcher; 2 | 3 | /** 4 | * Used to present the current state of a plugin version for CheckForUpdatesBoundary. 5 | * 6 | * @param modelId An identifier for this plugin. 7 | * Example `PluginModelId.byName("OwnPlots")`. 8 | * @param versionId An identifier for the version this plugin is currently at. 9 | * Example `PluginVersionId.byString("1.2.1")`. 10 | */ 11 | public record PluginVersionState( 12 | // No sum types. 13 | PluginModelId modelId, 14 | PluginVersionId versionId 15 | ) { } 16 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/models/Database.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.models; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Defines the structure of db.json object exposed by the mcpm server (GET /db). 7 | * 8 | * @param plugins A list of plugins that are provided by the server. 9 | */ 10 | public record Database(List plugins) { } 11 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/models/PluginCommand.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Command declared by a plugin's meta 9 | * 10 | * @param description Description 11 | * @param aliases Other names 12 | * @param permission Required permission 13 | * @param usage Usage help 14 | */ 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public record PluginCommand( 17 | String description, 18 | List aliases, 19 | String permission, 20 | String usage 21 | ) 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/models/PluginModel.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | 6 | import java.util.Comparator; 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | 11 | /** 12 | * This class describes one package. 13 | * 14 | * @param id Raw Plugin ID 15 | * @param versions Current & historical versions (sorted in time order) 16 | */ 17 | @JsonIgnoreProperties(ignoreUnknown = true) 18 | public record PluginModel( 19 | long id, 20 | List versions) 21 | { 22 | /** 23 | * Gets the latest PluginVersion from all PluginVersions of itself. 24 | * 25 | * @return The latest PluginVersion of itself, if it exists. 26 | */ 27 | @JsonIgnore 28 | public Optional getLatestPluginVersion() { 29 | return versions.stream().max(Comparator.comparingLong(PluginVersion::id)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/models/PluginTrackerModel.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.models; 2 | 3 | /** 4 | * Representation of a plugin for use in the local plugin tracker. 5 | */ 6 | @SuppressWarnings("unused") 7 | public class PluginTrackerModel { 8 | private String name; 9 | private boolean isManual; 10 | private Long versionId; 11 | private Long pluginId; 12 | 13 | /** 14 | * Default constructor. 15 | */ 16 | public PluginTrackerModel() 17 | { 18 | } 19 | 20 | /** 21 | * Constructor for PluginTrackerModel. 22 | * 23 | * @param name The name of the plugin. 24 | * @param isManual Whether the plugin is manually installed. 25 | * @param versionId The version ID of the plugin. 26 | * @param pluginId The plugin ID of the plugin. 27 | */ 28 | public PluginTrackerModel(String name, boolean isManual, Long versionId, Long pluginId) { 29 | this.name = name; 30 | this.isManual = isManual; 31 | this.versionId = versionId; 32 | this.pluginId = pluginId; 33 | } 34 | 35 | public String getName() { 36 | return name; 37 | } 38 | 39 | public Boolean isManual() { 40 | return isManual; 41 | } 42 | 43 | public Long getVersionId() { 44 | return versionId; 45 | } 46 | 47 | public Long getPluginId() { 48 | return pluginId; 49 | } 50 | 51 | public void setManual(boolean newBool) { 52 | this.isManual = newBool; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/models/PluginVersion.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | /** 6 | * This class describes one version of a package that the package manager can download. 7 | * 8 | * @param size Size of compressed archive 9 | * @param sha256 SHA256 Hash Checksum (for validation) 10 | * @param meta Meta info stored in plugin.yml 11 | */ 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | public record PluginVersion( 14 | long id, 15 | long size, 16 | String sha256, 17 | PluginYml meta 18 | ) 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearchInteractor.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcher; 4 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcherListener; 5 | 6 | 7 | /** 8 | * Handles searching within the database. 9 | * 10 | */ 11 | public class SearchInteractor implements SearchPackagesBoundary { 12 | private final DatabaseFetcher fetcher; 13 | private final DatabaseFetcherListener listener; 14 | private final SearcherFactory factory = new SearcherFactory(); 15 | 16 | /** 17 | * Creates a new database with the provided fetcher and upload listener. 18 | * 19 | * @param fetcher The fetcher that will be used to request the database object in boundary calls. 20 | * @param listener The listener that will receive updates if the database is downloaded from the internet. 21 | */ 22 | public SearchInteractor(DatabaseFetcher fetcher, DatabaseFetcherListener listener) { 23 | this.fetcher = fetcher; 24 | this.listener = listener; 25 | } 26 | 27 | /** 28 | * Searches for plugins based on the provided name, keyword, or command. 29 | * The input contains the type of search. 30 | * 31 | * @param input Record of inputs as provided in SearchPackagesInput. See it for more info. 32 | * @return Packages result. See the SearchPackagesResult record for more info. 33 | */ 34 | @Override 35 | public SearchPackagesResult search(SearchPackagesInput input) { 36 | var database = fetcher.fetchDatabase(!input.noCache(), listener); 37 | 38 | if (database == null) { 39 | return SearchPackagesResult.by(SearchPackagesResult.State.FAILED_TO_FETCH_DATABASE); 40 | } 41 | 42 | var searchStr = input.searchStr().toLowerCase(); 43 | if (searchStr.isEmpty()) 44 | return SearchPackagesResult.by(SearchPackagesResult.State.INVALID_INPUT); 45 | 46 | var plugins = database.plugins(); 47 | 48 | return new SearchPackagesResult(SearchPackagesResult.State.SUCCESS, 49 | factory.createSearcher(input).getSearchList(searchStr, plugins)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearchPackagesBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | /** 4 | * Interface for searching plugins. 5 | * 6 | */ 7 | public interface SearchPackagesBoundary { 8 | /** 9 | * Searches for plugins based on the provided name, keyword, or command. 10 | * The input contains the type of search. 11 | * 12 | * @param input Record of inputs as provided in SearchPackagesInput. See it for more info. 13 | * @return Packages result. See the SearchPackagesResult record for more info. 14 | */ 15 | SearchPackagesResult search(SearchPackagesInput input); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearchPackagesInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | /** 4 | * Input for the SearchPackagesBoundary. 5 | * 6 | * @param type Specifies the type of search. 7 | * @param searchStr String containing the name, keyword, or command to search by. 8 | * @param noCache If true, the ListPackagesBoundary will re-download the database before performing the request. 9 | * 10 | */ 11 | public record SearchPackagesInput(SearchPackagesType type, String searchStr, boolean noCache) { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearchPackagesResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | import org.hydev.mcpm.client.models.PluginModel; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Result returned from SearchPackagesBoundary. 9 | * 10 | * @param state The outcome of the request. Must be Success for the other values to be valid. 11 | * @param plugins The list of plugins corresponding to the search. 12 | * 13 | */ 14 | public record SearchPackagesResult(SearchPackagesResult.State state, List plugins) { 15 | /** 16 | * The outcome of the SearchPackagesResult. 17 | * For INVALID_INPUT, check that your string is non-empty. 18 | */ 19 | public enum State { 20 | SUCCESS, 21 | INVALID_INPUT, 22 | FAILED_TO_FETCH_DATABASE 23 | } 24 | 25 | /** 26 | * Creates an empty/default SearchPackagesResult with the provided state. 27 | * The default values are provided in order to easily create objects with failure States (ex. InvalidInput). 28 | * 29 | * @param state The state of the SearchPackagesResult. 30 | * @return A SearchPackagesResult object with the state property initialized and an empty list of plugins. 31 | */ 32 | public static SearchPackagesResult by(SearchPackagesResult.State state) { 33 | return new SearchPackagesResult(state, List.of()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearchPackagesType.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | /** 4 | * The type of searching package 5 | */ 6 | public enum SearchPackagesType { 7 | BY_NAME, 8 | BY_COMMAND, 9 | BY_KEYWORD 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/Searcher.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | import org.hydev.mcpm.client.models.PluginModel; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Interface for searchers that return Maps based on the list of plugins. 9 | * 10 | */ 11 | public interface Searcher { 12 | /** 13 | * Searches for plugins based on the provided user input. 14 | * 15 | * @param inp User input for the search, as a string. 16 | * @param plugins A list of all plugins in the database. 17 | * @return A list of plugins based on inp. 18 | */ 19 | List getSearchList(String inp, List plugins); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearcherByName.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | import org.hydev.mcpm.client.models.PluginModel; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Searcher that returns a map based on names. 12 | * 13 | */ 14 | public class SearcherByName implements Searcher { 15 | 16 | private Map> nameMap = null; 17 | 18 | /** 19 | * Returns a dictionary mapping the different names to the matching plugins. 20 | * Not case-sensitive. 21 | * 22 | * @param plugins A list of all plugins in the database. 23 | * @return A dictionary associating the name to the matching plugins. 24 | */ 25 | public Map> constructSearchMaps(List plugins) { 26 | Map> models = new HashMap<>(); 27 | for (PluginModel plugin : plugins) { 28 | // Get latest version 29 | plugin.getLatestPluginVersion().ifPresent(p -> 30 | { 31 | if (p.meta() != null && p.meta().name() != null && !p.meta().name().isBlank()) { 32 | String name = p.meta().name().toLowerCase(); 33 | if (!models.containsKey(name)) 34 | models.put(name, new ArrayList<>()); 35 | models.get(name).add(plugin); 36 | } 37 | }); 38 | } 39 | return models; 40 | } 41 | 42 | /** 43 | * Searches for plugins based on the provided user input. 44 | * 45 | * @param inp User input for the search. Should be a name as a string here. 46 | * @param plugins A list of all plugins in the database. 47 | * @return A list of plugins associated to inp. 48 | */ 49 | @Override 50 | public List getSearchList(String inp, List plugins) { 51 | // Instantiate if null 52 | if (nameMap == null) { 53 | nameMap = constructSearchMaps(plugins); 54 | } 55 | return nameMap.getOrDefault(inp, List.of()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/search/SearcherFactory.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.search; 2 | 3 | /** 4 | * Factory that produces Searcher objects based on the input type. 5 | * 6 | */ 7 | public class SearcherFactory { 8 | SearcherByName nameSearcher; 9 | SearcherByKeyword keywordSearcher; 10 | SearcherByCommand commandSearcher; 11 | 12 | /** 13 | * Returns the new searcher object based on the input type. 14 | * 15 | * @param input Contains the search type in particular. See SearchPackagesInput for details. 16 | */ 17 | public Searcher createSearcher(SearchPackagesInput input) { 18 | return switch (input.type()) { 19 | case BY_NAME -> 20 | nameSearcher == null ? 21 | nameSearcher = new SearcherByName() : nameSearcher; 22 | 23 | case BY_COMMAND -> 24 | commandSearcher == null ? 25 | commandSearcher = new SearcherByCommand() : commandSearcher; 26 | 27 | case BY_KEYWORD -> 28 | keywordSearcher == null ? 29 | keywordSearcher = new SearcherByKeyword() : keywordSearcher; 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/FileRemove.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | /** 4 | * Interface for removing a file 5 | */ 6 | 7 | public interface FileRemove { 8 | /** 9 | * Remove file for given plugin (step 2 and 3 of Uninstaller use case) 10 | * 11 | * @param pluginName Name of the plugin to remove 12 | * @return int 0 - true, 1 - NOT_FOUND, 2 - FAILED_TO_DELETE 13 | */ 14 | FileRemoveResult removeFile(String pluginName); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/FileRemoveResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | /** 4 | * Result for removing a file 5 | */ 6 | public enum FileRemoveResult 7 | { 8 | SUCCESS, 9 | NOT_FOUND, 10 | FAILED_TO_DELETE 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/PluginRemover.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | import org.hydev.mcpm.client.loader.LocalJarBoundary; 4 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Removes file for FileRemove.java 10 | */ 11 | public class PluginRemover implements FileRemove { 12 | private final LocalJarBoundary jarFinder; 13 | 14 | public PluginRemover(LocalJarBoundary jarFinder) { 15 | this.jarFinder = jarFinder; 16 | } 17 | 18 | @Override 19 | public FileRemoveResult removeFile(String pluginName) { 20 | // 2. If it isn't loaded, find the plugin jar file in local file system 21 | // (This will throw PluginNotFoundException when a plugin of the name in the file system 22 | // could not be found). 23 | File jar; 24 | try { 25 | jar = jarFinder.findJar(pluginName); 26 | } catch (PluginNotFoundException e) { 27 | return FileRemoveResult.NOT_FOUND; 28 | } 29 | 30 | // 3. Delete plugin jar 31 | if (!jar.delete()) { 32 | return FileRemoveResult.FAILED_TO_DELETE; 33 | } 34 | return FileRemoveResult.SUCCESS; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/UninstallBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | /** 4 | * Uninstalls a plugin 5 | */ 6 | public interface UninstallBoundary { 7 | /** 8 | * Uninstalls plugin based on its given name 9 | * 10 | * @param input Uninstall input 11 | */ 12 | UninstallResult uninstall(UninstallInput input); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/UninstallInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | /** 4 | * Uninstall Plugin input 5 | * 6 | * @param name Plugin name 7 | * @param recursive remove dependencies or not 8 | * @param delete Whether to delete the file after unloading 9 | */ 10 | public record UninstallInput( 11 | String name, 12 | boolean recursive, 13 | boolean delete 14 | ) 15 | { 16 | /** 17 | * Default constructor with delete=true 18 | * 19 | * @param name Plugin name 20 | * @param recursive remove dependencies or not 21 | */ 22 | public UninstallInput(String name, boolean recursive) 23 | { 24 | this(name, recursive, true); 25 | } 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/uninstall/UninstallResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.uninstall; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Result for uninstalling 8 | * 9 | * @param state State of uninstall 10 | * @param dependencies States for uninstall dependencies 11 | */ 12 | public record UninstallResult(State state, Map dependencies) { 13 | public UninstallResult(State state) { 14 | this(state, new HashMap<>()); 15 | } 16 | 17 | /** 18 | * Result state for uninstall 19 | */ 20 | public enum State { 21 | NOT_FOUND, 22 | FAILED_TO_DELETE, 23 | SUCCESS 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/CheckForUpdatesBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | /** 4 | * Defines how a user can search for plugin updates. 5 | */ 6 | public interface CheckForUpdatesBoundary { 7 | /** 8 | * Looks through the database for plugins in CheckForUpdatesInput that have an updated version. 9 | * 10 | * @param forInput A list of all plugins + plugin version identifiers that will be checked for updates. 11 | * @return A list of plugins that need updates in CheckForUpdatesResult#updatable. 12 | */ 13 | CheckForUpdatesResult updates(CheckForUpdatesInput forInput); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/CheckForUpdatesInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | import org.hydev.mcpm.client.matcher.PluginVersionState; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Input for the CheckForUpdatesBoundary. 9 | * 10 | * @param states A list of all plugins that should be considered for updates. 11 | * @param noCache If true, the ListPackagesBoundary will re-download the database before performing the request. 12 | */ 13 | public record CheckForUpdatesInput( 14 | List states, 15 | boolean noCache 16 | ) { } 17 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/CheckForUpdatesResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | import org.hydev.mcpm.client.matcher.PluginVersionState; 4 | import org.hydev.mcpm.client.models.PluginModel; 5 | 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | /** 10 | * Result returned from CheckForUpdatesBoundary. 11 | * If a plugin is in the mismatched list, you might want to ask the user if they can be removed. 12 | * 13 | * @param state The outcome of the request. Must be Success for the other values to be valid. 14 | * @param updatable A map from version ids to all plugins that require updates. 15 | * @param mismatched A list of all plugins that were not matched to any Plugin in the database. 16 | */ 17 | public record CheckForUpdatesResult( 18 | State state, 19 | Map updatable, 20 | Set mismatched 21 | ) { 22 | /** 23 | * The outcome of the CheckForUpdatesResult. 24 | */ 25 | public enum State { 26 | SUCCESS, 27 | INVALID_INPUT, 28 | FAILED_TO_FETCH_DATABASE, 29 | } 30 | 31 | /** 32 | * Creates an empty CheckForUpdatesResult with the provided state. 33 | * The default values are provided in order to easily create objects with failure States. 34 | * 35 | * @param state The state of the CheckForUpdatesResult. 36 | * @return A CheckForUpdatesResult object with state initialized and dummy values for the other elements. 37 | */ 38 | public static CheckForUpdatesResult by(CheckForUpdatesResult.State state) { 39 | return new CheckForUpdatesResult(state, Map.of(), Set.of()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/UpdateBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | /** 4 | * Boundary for the update command. 5 | */ 6 | public interface UpdateBoundary { 7 | /** 8 | * Request an update for the plugins in UpdateInput. 9 | * 10 | * @param input An input containing information on the plugins to update. 11 | * @return A result detailing which plugins were updated. 12 | */ 13 | UpdateResult update(UpdateInput input); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/UpdateInput.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Input object to be passed to UpdateBoundary. 7 | * 8 | * @param pluginNames A list of all plugin names that should be updated. 9 | * An empty list will update all plugins. 10 | * @param load Whether to reload plugins that are installed. 11 | * Ignored on a CLI environment. 12 | * @param noCache Whether to force fetch the database before checking updates. 13 | */ 14 | public record UpdateInput( 15 | List pluginNames, 16 | boolean load, 17 | boolean noCache 18 | ) { 19 | /** 20 | * True if the input object should update all plugins (an empty pluginNames list). 21 | * 22 | * @return Whether to update all plugins. 23 | */ 24 | public boolean updateAll() { 25 | return pluginNames.isEmpty(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/UpdateOutcome.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | /** 6 | * Represents an outcome from an Update request (was the plugin updated? etc.). 7 | * 8 | * @param state What happened to the plugin (updated, ignored, ...) 9 | * @param initialVersion The currently installed/known version of the plugin. 10 | * @param destinationVersion The version that the plugin was updated to (null state != SUCCESS). 11 | */ 12 | public record UpdateOutcome( 13 | State state, 14 | @Nullable String initialVersion, 15 | @Nullable String destinationVersion 16 | ) { 17 | /** 18 | * Represents what was done to the plugin during an update. 19 | */ 20 | public enum State { 21 | /** 22 | * Set when a plugin was not found in the database, so it was not updated. 23 | */ 24 | MISMATCHED, 25 | /** 26 | * Set when this plugin was requested to be updated, but could not be found on the system. 27 | */ 28 | NOT_INSTALLED, 29 | /** 30 | * Set when something went wrong related to fetching plugin information. 31 | */ 32 | NETWORK_ERROR, 33 | /** 34 | * Set when the version of the plugin installed is the same as the one in the database. 35 | * Use --no-cache to force an update of the database. 36 | */ 37 | UP_TO_DATE, 38 | /** 39 | * Set when the plugin was out of date, so it was updated to the latest version. 40 | * The version that it was updated to is in destinationVersion. 41 | */ 42 | UPDATED; 43 | 44 | /** 45 | * Returns true in success states. 46 | * 47 | * @return True if this is either SUCCESS or UP_TO_DATE. 48 | */ 49 | public boolean success() { 50 | return switch (this) { 51 | case MISMATCHED, NOT_INSTALLED, NETWORK_ERROR -> false; 52 | case UP_TO_DATE, UPDATED -> true; 53 | }; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/client/updater/UpdateResult.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.updater; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Information on the outcome of an update command. 7 | * 8 | * @param state The result of the update command. 9 | * SUCCESS is required to have vaild values for other fields. 10 | * @param outcomes A map mapping plugin names to UpdateOutcome objects detailing whether they were updated. 11 | */ 12 | public record UpdateResult( 13 | State state, 14 | Map outcomes 15 | ) { 16 | /** 17 | * The result of the update command. 18 | */ 19 | public enum State { 20 | INTERNAL_ERROR, 21 | FAILED_TO_FETCH_DATABASE, 22 | SUCCESS 23 | } 24 | 25 | /** 26 | * Provides dummy values of UpdateResult for various states. 27 | * 28 | * @param state The state of the returned UpdateResult. 29 | * @return A UpdateResult object. 30 | */ 31 | public static UpdateResult by(State state) { 32 | return new UpdateResult(state, Map.of()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/server/spiget/SpigetResource.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.server.spiget; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | /** 7 | * Spiget Resource POJO for endpoint api.spiget.org/v2/resources 8 | * 9 | * @param id Resource ID 10 | * @param name Displayed name (NOT UNIQUE/FINAL!) 11 | * @param tag Displayed description 12 | * @param external Whether it's an external resource 13 | * @param likes Number of likes 14 | * @param testedVersions Tested versions 15 | * @param links Links 16 | * @param releaseDate Release timestamp in ms 17 | * @param updateDate Update timestamp in ms 18 | * @param downloads Number of downloads 19 | * @param existenceStatus Existence status, IDK what this means 20 | * @param version Latest version ID and UUID 21 | */ 22 | @SuppressWarnings("unused") 23 | public record SpigetResource( 24 | long id, 25 | String name, 26 | String tag, 27 | boolean external, 28 | long likes, 29 | List testedVersions, 30 | Map links, 31 | long releaseDate, 32 | long updateDate, 33 | long downloads, 34 | long existenceStatus, 35 | SpigetVersion version 36 | ) 37 | { 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/server/spiget/SpigetVersion.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.server.spiget; 2 | 3 | /** 4 | * Spiget Version POJO for endpoint api.spiget.org/v2/resources/{resource_id}/versions 5 | * 6 | * @param uuid Unique ID for the version 7 | * @param id Numeric ID of the version 8 | */ 9 | @SuppressWarnings("unused") 10 | public record SpigetVersion( 11 | String uuid, 12 | long id 13 | ) 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/ConsoleUtils.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import java.io.FileDescriptor; 4 | import java.io.FileOutputStream; 5 | import java.io.PrintStream; 6 | 7 | /** 8 | * Xterm console interaction utilities 9 | */ 10 | public class ConsoleUtils 11 | { 12 | public static final PrintStream RAW_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out)); 13 | private final PrintStream out; 14 | 15 | public ConsoleUtils(PrintStream out) 16 | { 17 | this.out = out; 18 | } 19 | 20 | public ConsoleUtils() 21 | { 22 | this(RAW_OUT); 23 | } 24 | 25 | /** 26 | * Reset the cursor to the first column (left-most position) 27 | */ 28 | public void curCol1() 29 | { 30 | out.print('\r'); 31 | out.flush(); 32 | } 33 | 34 | /** 35 | * Move cursor up by n lines. 36 | * 37 | * @param lines n (Negative to move down) 38 | */ 39 | public void curUp(int lines) 40 | { 41 | if (lines == 0) return; 42 | 43 | if (lines > 0) out.printf("\033[%dA", lines); 44 | else out.printf("\033[%dB", -lines); 45 | curCol1(); 46 | } 47 | 48 | /** 49 | * Erases line from the current cursor position till the end of the line 50 | */ 51 | public void eraseLine() 52 | { 53 | curCol1(); 54 | out.print("\033[K"); 55 | out.flush(); 56 | } 57 | 58 | /** 59 | * Erase screen and move to top left 60 | */ 61 | void clear() { 62 | out.print("\033[H\033[2J"); 63 | out.flush(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.apache.hc.client5.http.fluent.Request; 4 | import org.apache.hc.core5.util.Timeout; 5 | 6 | import java.io.IOException; 7 | import java.util.stream.IntStream; 8 | 9 | /** 10 | * Network utilities 11 | */ 12 | public class NetworkUtils 13 | { 14 | private static final Timeout ONE_S = Timeout.ofSeconds(1); 15 | private static final int PING_ITERS = 3; 16 | private static boolean pingInitialized = false; 17 | 18 | /** 19 | * Measure the ping (internet connectivity delay) to an url 20 | * 21 | * @param url Target url 22 | * @return Ping in milliseconds, or -1 if it cannot connect 23 | */ 24 | public static int ping(String url) 25 | { 26 | // Preheat HTTP request module so that subsequent access return similar delay 27 | if (!pingInitialized) 28 | { 29 | try 30 | { 31 | Request.head("https://every1dns.com").connectTimeout(ONE_S).execute(); 32 | } 33 | catch (IOException ignored) { } 34 | pingInitialized = true; 35 | } 36 | 37 | url = (url.startsWith("http://") || url.startsWith("https://")) ? url : "http://" + url; 38 | final var u = url; 39 | 40 | var start = System.currentTimeMillis(); 41 | 42 | // Do request and check code 43 | var hasSuccess = IntStream.range(0, PING_ITERS).anyMatch(i -> 44 | { 45 | try 46 | { 47 | // Status code < 400 means HTTP success for HEAD (either 200 success or 300 redirect) 48 | return Request.head(u).connectTimeout(ONE_S).responseTimeout(ONE_S) 49 | .execute().returnResponse().getCode() < 400; 50 | } 51 | catch (IOException e) 52 | { 53 | return false; 54 | } 55 | }); 56 | 57 | if (!hasSuccess) return -1; 58 | 59 | // Return time 60 | return (int) (System.currentTimeMillis() - start) / PING_ITERS; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/Pair.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import java.util.Map; 4 | import java.util.stream.Collector; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * Simple unmodifiable generic pair 9 | */ 10 | public record Pair(K k, V v) implements Map.Entry 11 | { 12 | /** 13 | * Exception when trying to modify an unmodifiable field 14 | */ 15 | public static class UnmodifiableException extends RuntimeException {} 16 | 17 | @Override 18 | public K getKey() 19 | { 20 | return k; 21 | } 22 | 23 | @Override 24 | public V getValue() 25 | { 26 | return v; 27 | } 28 | 29 | @Override 30 | public V setValue(V v) 31 | { 32 | // Cannot be modified 33 | throw new UnmodifiableException(); 34 | } 35 | 36 | /** 37 | * Collect a stream of pairs to a map 38 | * 39 | * @return Collector 40 | */ 41 | public static Collector, ?, Map> toMap() 42 | { 43 | return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue); 44 | } 45 | 46 | /** 47 | * Simpler constructor 48 | * 49 | * @param k Key 50 | * @param v Value 51 | * @return Pair[K, V] 52 | */ 53 | public static Pair of(K k, V v) 54 | { 55 | return new Pair<>(k, v); 56 | } 57 | 58 | @Override 59 | public String toString() 60 | { 61 | return k.toString() + ": " + v.toString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/TemporaryDir.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Comparator; 8 | 9 | /** 10 | * This class handles temporary directories like Python's tempfile.TemporaryDirectory: 11 | *

12 | * {@snippet 13 | * with tempfile.TemporaryDirectory() as tmpdirname: 14 | * print('created temporary directory', tmpdirname) 15 | * # directory and contents have been removed 16 | * } 17 | *

18 | * With this class, you can do something like: 19 | *

20 | * {@snippet 21 | * try (var tmp = new TemporaryDir()) 22 | * { 23 | * // Do something with tmp 24 | * } 25 | * // directory and contents have been removed 26 | * } 27 | */ 28 | public class TemporaryDir implements AutoCloseable 29 | { 30 | public final File path; 31 | 32 | /** 33 | * Initialize temporary dir 34 | */ 35 | public TemporaryDir() 36 | { 37 | try 38 | { 39 | path = Files.createTempDirectory("mcpm-" + System.currentTimeMillis()).toFile(); 40 | 41 | //noinspection ResultOfMethodCallIgnored 42 | path.mkdirs(); 43 | } 44 | catch (IOException e) 45 | { 46 | // Would never happen unless the temporary directory cannot be written, in which case 47 | // the user would have far more important things to worry about 48 | throw new RuntimeException(e); 49 | } 50 | } 51 | 52 | /** 53 | * Close temporary dir 54 | */ 55 | @Override 56 | public void close() 57 | { 58 | // Recursively delete files on exit 59 | try (var w = Files.walk(path.toPath())) 60 | { 61 | //noinspection ResultOfMethodCallIgnored 62 | w.sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete); 63 | } 64 | catch (IOException e) 65 | { 66 | throw new RuntimeException(e); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/UnitConverter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | 6 | /** 7 | * Unit converter utility class 8 | */ 9 | public class UnitConverter 10 | { 11 | /** 12 | * Pair of unit and size 13 | * 14 | * @param unit Unit (string) 15 | * @param size Size (decimal number) in that unit 16 | */ 17 | public record UnitSize(String unit, Double size) {} 18 | 19 | /** 20 | * Automatically selects the best unit to represent binary size. 21 | * 22 | * @param size Original size in bytes 23 | * @return Pair[Unit name, Converted value] 24 | */ 25 | public static UnitSize autoBinarySize(double size) 26 | { 27 | for (var unit : List.of("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB")) 28 | { 29 | if (Math.abs(size) < 1024) return new UnitSize(unit, size); 30 | size /= 1024d; 31 | } 32 | 33 | throw new AssertionError(String.format("Size %s too large for formatting", size)); 34 | } 35 | 36 | /** 37 | * Generate a speed formatter for unit conversion 38 | * 39 | * @return Speed formatter function 40 | */ 41 | public static Function binarySpeedFormatter() 42 | { 43 | return (d) -> { 44 | var size = autoBinarySize(d); 45 | return String.format("%5.1f %2$3s/s", size.size(), size.unit()); 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/ZstdException.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Errors during the zstd compression/decompression 7 | */ 8 | public class ZstdException extends IOException 9 | { 10 | public ZstdException(Throwable cause) 11 | { 12 | super(cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/arrays/FixedWindowSum.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils.arrays; 2 | 3 | /** 4 | * A data structure supporting appending of index-value pairs 5 | * and querying the sum of all values in a fixed window size. 6 | * This only supports addition non-decreasing index pairs, and queries of non-increasing indices, 7 | * and queries of a fixed window size. 8 | */ 9 | public interface FixedWindowSum { 10 | /** 11 | * Add an index-value pair to the window. 12 | * 13 | * @param index the index of the pair 14 | * @param val value to add to data structure 15 | */ 16 | void add(long index, long val); 17 | 18 | /** 19 | * Query the sum of all values that have an index within the window size (exclusive) that ends at the given index. 20 | * 21 | * @param index the given index 22 | * @return the result of the query 23 | */ 24 | long sum(long index); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/hydev/mcpm/utils/arrays/SlidingWindow.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils.arrays; 2 | 3 | import org.hydev.mcpm.utils.Pair; 4 | 5 | import java.util.ArrayDeque; 6 | import java.util.Queue; 7 | 8 | /** 9 | * An implementation of FixedWindowSum using a queue. 10 | */ 11 | public class SlidingWindow implements FixedWindowSum { 12 | private long sum; 13 | private long window; 14 | private final Queue> queue = new ArrayDeque<>(); 15 | 16 | public FixedWindowSum setWindowSize(long window) { 17 | this.window = window; 18 | return this; 19 | } 20 | 21 | @Override 22 | public void add(long index, long inc) { 23 | queue.add(new Pair<>(index, inc)); 24 | sum += inc; // increase sum after adding a new value 25 | restore(index); 26 | } 27 | 28 | @Override 29 | public long sum(long index) { 30 | restore(index); 31 | return sum; 32 | } 33 | 34 | /** 35 | * Restore the invariant that all values in the queue are within the window 36 | * 37 | * @param index index of the newest query/insert 38 | */ 39 | private void restore(long index) { 40 | // remove values outside the window, O(1) amortized 41 | while (!queue.isEmpty() && queue.peek().getKey() < index - window) { 42 | var element = queue.poll(); // remove the oldest value if it is outside the window 43 | sum -= element.getValue(); // decrement sum after removing values 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | main: org.hydev.mcpm.SpigotEntry 2 | name: MCPM 3 | version: '@version@' 4 | description: Minecraft Package Manager for Bukkit/Spiget Servers 5 | api-version: 1.18 6 | authors: [Hykilpikonna, 1whatleytay, MstrPikachu, kchprog, jerryzhu509, thudoan1706, 7 | aanushkasharma] 8 | website: https://github.com/CSC207-2022F-UofT/mcpm 9 | commands: 10 | mcpm: 11 | description: Minecraft package manager 12 | usage: 'View help: /mcpm -h' 13 | permission: mcpm.use 14 | libraries: 15 | - net.sourceforge.argparse4j:argparse4j:0.9.0 16 | - org.apache.httpcomponents.client5:httpclient5-fluent:5.2-beta1 17 | - com.fasterxml.jackson.core:jackson-core:2.14.0 18 | - com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.0 19 | - com.google.guava:guava:31.1-jre 20 | - org.jetbrains:annotations:23.0.0 21 | - org.fusesource.jansi:jansi:2.4.0 22 | - org.apache.commons:commons-lang3:3.12.0 23 | - com.github.luben:zstd-jni:1.5.2-5 24 | - io.airlift:aircompressor:0.21 25 | - org.jetbrains.kotlin:kotlin-stdlib:1.8.0 26 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/ExportParserTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments; 2 | 3 | import org.hydev.mcpm.client.arguments.mock.MockExportBoundary; 4 | import org.hydev.mcpm.client.commands.controllers.ExportController; 5 | import org.hydev.mcpm.client.export.ExportPluginsInput; 6 | import org.hydev.mcpm.client.export.ExportPluginsResult; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertNull; 12 | 13 | /** 14 | * Contains tests for testing the export controller and parser objects. 15 | * E.g. whether strings commands will result in correct inputs, call the right methods in the boundary, etc. 16 | */ 17 | public class ExportParserTest { 18 | private MockExportBoundary exporter; 19 | private ExportController controller; 20 | 21 | /** 22 | * Initializes the various fields (controllers, etc.) before a test starts. 23 | */ 24 | @BeforeEach 25 | public void setup() { 26 | exporter = new MockExportBoundary(); 27 | controller = new ExportController(exporter, (presenter, log) -> { }); 28 | } 29 | 30 | /** 31 | * Tests whether a controller will still fail gracefully when passed a default result. 32 | */ 33 | @Test 34 | void testControllerWithFailState() { 35 | exporter.setDefaultResult(ExportPluginsResult.State.FAILED); 36 | 37 | controller.export(new ExportPluginsInput("literal", null), log -> { }); 38 | var inputs = exporter.getInputs(); 39 | assertEquals(inputs.size(), 1); 40 | 41 | var input = inputs.get(0); 42 | // Should still receive a request. Not going to check log again since it's presentation logic. 43 | assertEquals(input.type(), "literal"); 44 | assertNull(input.out()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/ParserFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments; 2 | 3 | import org.hydev.mcpm.client.arguments.parsers.CommandConfigurator; 4 | import org.junit.jupiter.api.Tag; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Set; 8 | import java.util.stream.Collectors; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | /** 13 | * Contains tests for testing the parser factory class. 14 | * Generally, we only want to make sure that we have entries for all the basic commands 15 | * that are required for our app to work. 16 | */ 17 | public class ParserFactoryTest { 18 | /** 19 | * Tests if ParserFactory will generate a list of plugins that has entries for all main commands. 20 | * Ex. export, list, search... 21 | * The use should be able to invoke these commands so it's important that we support this. 22 | */ 23 | @Test 24 | @Tag("IntegrationTest") 25 | void testBasePlugins() { 26 | var interactors = new InteractorFactory(false); 27 | var controllers = new ControllerFactory(interactors); 28 | 29 | var parsers = ParserFactory.baseParsers(controllers); 30 | 31 | var names = parsers.stream() 32 | .map(CommandConfigurator::name) 33 | .collect(Collectors.toSet()); 34 | 35 | var expected = Set.of( 36 | "export", 37 | "list", 38 | "search", 39 | "mirror", 40 | "info", 41 | "install", 42 | "refresh", 43 | "page", 44 | "uninstall", 45 | "update" 46 | ); 47 | 48 | assertTrue(names.containsAll(expected)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockExportBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.export.ExportPluginsBoundary; 4 | import org.hydev.mcpm.client.export.ExportPluginsInput; 5 | import org.hydev.mcpm.client.export.ExportPluginsResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the ExportPluginsBoundary interface for testing. 12 | */ 13 | public class MockExportBoundary implements ExportPluginsBoundary { 14 | private final List inputs = new ArrayList<>(); 15 | 16 | private ExportPluginsResult.State defaultResult = ExportPluginsResult.State.SUCCESS; 17 | 18 | @Override 19 | public ExportPluginsResult export(ExportPluginsInput input) { 20 | inputs.add(input); 21 | 22 | return new ExportPluginsResult(defaultResult, "", null); 23 | } 24 | 25 | /** 26 | * Sets the default result that this interface will return when invoked. 27 | * 28 | * @param defaultResult The default return value for the export method. 29 | */ 30 | public void setDefaultResult(ExportPluginsResult.State defaultResult) { 31 | this.defaultResult = defaultResult; 32 | } 33 | 34 | /** 35 | * Gets a list of all inputs that this interface was invoked with. 36 | * 37 | * @return A list of input objects. 38 | */ 39 | public List getInputs() { 40 | return List.copyOf(inputs); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockInstallBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.installer.InstallBoundary; 4 | import org.hydev.mcpm.client.installer.input.InstallInput; 5 | import org.hydev.mcpm.client.installer.output.InstallResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the InstallBoundary interface for testing. 12 | */ 13 | public class MockInstallBoundary implements InstallBoundary { 14 | private final List inputs = new ArrayList<>(); 15 | 16 | @Override 17 | public List installPlugin(InstallInput installInput) { 18 | inputs.add(installInput); 19 | 20 | return List.of(new InstallResult(InstallResult.Type.SUCCESS_INSTALLED, installInput.name())); 21 | } 22 | 23 | /** 24 | * Gets a list of all inputs that this interface was invoked with. 25 | * 26 | * @return A list of input objects. 27 | */ 28 | public List getInputs() { 29 | return List.copyOf(inputs); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockListBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.list.ListAllBoundary; 4 | import org.hydev.mcpm.client.list.ListType; 5 | import org.hydev.mcpm.client.models.PluginYml; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the ListAllBoundary interface for testing. 12 | */ 13 | public class MockListBoundary implements ListAllBoundary { 14 | private final List types = new ArrayList<>(); 15 | 16 | @Override 17 | public List listAll(ListType parameter) { 18 | types.add(parameter); 19 | 20 | return List.of(); 21 | } 22 | 23 | /** 24 | * Gets a list of all types that this interface was invoked with. 25 | * 26 | * @return A list of type objects. 27 | */ 28 | public List getTypes() { 29 | return List.copyOf(types); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockLoadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.loader.LoadBoundary; 4 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Provides a mock implementation of the LoadBoundary interface for testing. 11 | */ 12 | public class MockLoadBoundary implements LoadBoundary { 13 | private final List names = new ArrayList<>(); 14 | 15 | private boolean defaultResult = true; 16 | private boolean throwsNotFound = false; 17 | 18 | @Override 19 | public boolean loadPlugin(String name) throws PluginNotFoundException { 20 | names.add(name); 21 | 22 | if (throwsNotFound) { 23 | throw new PluginNotFoundException(name); 24 | } 25 | 26 | return defaultResult; 27 | } 28 | 29 | /** 30 | * Sets the default result that this interface will return when invoked. 31 | * 32 | * @param result The default return value for the load method. 33 | */ 34 | public void setDefaultResult(boolean result) { 35 | this.defaultResult = result; 36 | } 37 | 38 | /** 39 | * Sets whether this interface should throw an exception when invoked. 40 | * 41 | * @param notFound If true, this interface will throw an exception on invocation. 42 | */ 43 | public void setThrowsNotFound(boolean notFound) { 44 | this.throwsNotFound = notFound; 45 | } 46 | 47 | /** 48 | * Gets a list of all names that this interface was invoked with. 49 | * 50 | * @return A list of strings that represent plugin names. 51 | */ 52 | public List getNames() { 53 | return List.copyOf(names); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockRefreshFetcher.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcher; 4 | import org.hydev.mcpm.client.database.fetcher.DatabaseFetcherListener; 5 | import org.hydev.mcpm.client.models.Database; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the DatabaseFetcher interface for testing RefreshController. 12 | */ 13 | public class MockRefreshFetcher implements DatabaseFetcher { 14 | private boolean fetched = false; 15 | 16 | private @Nullable Database defaultResult = new Database(List.of()); 17 | 18 | @Override 19 | public @Nullable Database fetchDatabase(boolean cache, DatabaseFetcherListener listener) { 20 | if (!cache) { 21 | fetched = true; 22 | } 23 | 24 | return defaultResult; 25 | } 26 | 27 | public boolean getFetched() { 28 | return fetched; 29 | } 30 | 31 | /** 32 | * Sets the default database result that this interface will return when invoked. 33 | * 34 | * @param database The default return value for the load method. 35 | */ 36 | public void setDefaultResult(@Nullable Database database) { 37 | this.defaultResult = database; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockReloadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 4 | import org.hydev.mcpm.client.loader.ReloadBoundary; 5 | 6 | import java.util.List; 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * Provides a mock implementation of the ReloadBoundary interface for testing. 11 | */ 12 | public class MockReloadBoundary implements ReloadBoundary { 13 | private final List names = new ArrayList<>(); 14 | 15 | private boolean throwsNotFound = false; 16 | 17 | @Override 18 | public void reloadPlugin(String name) throws PluginNotFoundException { 19 | names.add(name); 20 | 21 | if (throwsNotFound) { 22 | throw new PluginNotFoundException(name); 23 | } 24 | } 25 | 26 | /** 27 | * Sets whether this interface should throw an exception when invoked. 28 | * 29 | * @param notFound If true, this interface will throw an exception on invocation. 30 | */ 31 | public void setThrowsNotFound(boolean notFound) { 32 | this.throwsNotFound = notFound; 33 | } 34 | 35 | /** 36 | * Gets a list of all names that this interface was invoked with. 37 | * 38 | * @return A list of strings that represent plugin names. 39 | */ 40 | public List getNames() { 41 | return List.copyOf(names); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockSearchBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.search.SearchPackagesBoundary; 4 | import org.hydev.mcpm.client.search.SearchPackagesInput; 5 | import org.hydev.mcpm.client.search.SearchPackagesResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the SearchBoundary interface for testing. 12 | */ 13 | public class MockSearchBoundary implements SearchPackagesBoundary { 14 | private final List inputs = new ArrayList<>(); 15 | 16 | @Override 17 | public SearchPackagesResult search(SearchPackagesInput input) { 18 | inputs.add(input); 19 | 20 | return SearchPackagesResult.by(SearchPackagesResult.State.SUCCESS); 21 | } 22 | 23 | /** 24 | * Gets a list of all inputs that this interface was invoked with. 25 | * 26 | * @return A list of input objects. 27 | */ 28 | public List getInputs() { 29 | return List.copyOf(inputs); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockSearchPresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.SearchResultPresenter; 4 | import org.hydev.mcpm.client.search.SearchPackagesResult; 5 | 6 | import java.util.function.Consumer; 7 | 8 | /** 9 | * This class is a mock search presenter that logs all inputs to a string. 10 | * It is used for tests involving a search presenter. 11 | */ 12 | public class MockSearchPresenter implements SearchResultPresenter { 13 | @Override 14 | public void displayResult(SearchPackagesResult searchResult, Consumer log) { 15 | /* do nothing */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockUninstallBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.uninstall.UninstallBoundary; 4 | import org.hydev.mcpm.client.uninstall.UninstallInput; 5 | import org.hydev.mcpm.client.uninstall.UninstallResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the UninstallBoundary interface for testing. 12 | */ 13 | public class MockUninstallBoundary implements UninstallBoundary { 14 | private final List inputs = new ArrayList<>(); 15 | private UninstallResult.State defaultState = UninstallResult.State.SUCCESS; 16 | 17 | @Override 18 | public UninstallResult uninstall(UninstallInput input) { 19 | inputs.add(input); 20 | 21 | return new UninstallResult(defaultState); 22 | } 23 | 24 | /** 25 | * Changes the default state returned for this uninstall result. 26 | * As this is a mock class, I'm not really reserved about adding mutator methods. 27 | * It adds some complexity in setup if I avoid this. 28 | * 29 | * @param state The state to set. 30 | */ 31 | public void setDefaultState(UninstallResult.State state) { 32 | defaultState = state; 33 | } 34 | 35 | /** 36 | * Gets a list of all inputs that this interface was invoked with. 37 | * 38 | * @return A list of input objects. 39 | */ 40 | public List getInputs() { 41 | return List.copyOf(inputs); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockUnloadBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.loader.PluginNotFoundException; 4 | import org.hydev.mcpm.client.loader.UnloadBoundary; 5 | 6 | import java.io.File; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the UnloadBoundary interface for testing. 12 | */ 13 | public class MockUnloadBoundary implements UnloadBoundary { 14 | private final List names = new ArrayList<>(); 15 | 16 | private boolean throwsNotFound = false; 17 | 18 | @Override 19 | public File unloadPlugin(String name) throws PluginNotFoundException { 20 | names.add(name); 21 | 22 | if (throwsNotFound) { 23 | throw new PluginNotFoundException(name); 24 | } 25 | 26 | return null; 27 | } 28 | 29 | /** 30 | * Sets whether this interface should throw an exception when invoked. 31 | * 32 | * @param notFound If true, this interface will throw an exception on invocation. 33 | */ 34 | public void setThrowsNotFound(boolean notFound) { 35 | this.throwsNotFound = notFound; 36 | } 37 | 38 | /** 39 | * Gets a list of all names that this interface was invoked with. 40 | * 41 | * @return A list of strings that represent plugin names. 42 | */ 43 | public List getNames() { 44 | return List.copyOf(names); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/arguments/mock/MockUpdateBoundary.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.arguments.mock; 2 | 3 | import org.hydev.mcpm.client.updater.UpdateBoundary; 4 | import org.hydev.mcpm.client.updater.UpdateInput; 5 | import org.hydev.mcpm.client.updater.UpdateResult; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Provides a mock implementation of the UpdateBoundary interface for testing. 12 | */ 13 | public class MockUpdateBoundary implements UpdateBoundary { 14 | private final List inputs = new ArrayList<>(); 15 | 16 | @Override 17 | public UpdateResult update(UpdateInput input) { 18 | inputs.add(input); 19 | 20 | return UpdateResult.by(UpdateResult.State.SUCCESS); 21 | } 22 | 23 | /** 24 | * Gets a list of all inputs that this interface was invoked with. 25 | * 26 | * @return A list of input objects. 27 | */ 28 | public List getInputs() { 29 | return List.copyOf(inputs); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/commands/presenter/TableTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.commands.presenter; 2 | 3 | import org.hydev.mcpm.client.display.presenters.Table; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.List; 7 | 8 | import static org.hydev.mcpm.utils.ColorLogger.printc; 9 | 10 | /** 11 | * Tests for FormatUtils 12 | */ 13 | class TableTest 14 | { 15 | @Test 16 | void tabulate() 17 | { 18 | printc(Table.tabulate(List.of( 19 | List.of("Azalea", "meow", "Cakes without eggs or sugar"), 20 | List.of("Lindsey", "meow", "Burgers") 21 | ), List.of(":Contributor", "Centered", "Favorite Food:"))); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/database/MirrorSelectorTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database; 2 | 3 | import org.hydev.mcpm.client.local.MirrorSelector; 4 | import org.junit.jupiter.api.Tag; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.IOException; 8 | 9 | import static org.hydev.mcpm.utils.NetworkUtils.ping; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | import static org.junit.jupiter.api.Assumptions.assumeTrue; 12 | 13 | /** 14 | * This class contains tests for the MirrorSelector class. 15 | * This class also makes networking requests. 16 | */ 17 | class MirrorSelectorTest 18 | { 19 | private static final MirrorSelector mi = new MirrorSelector(); 20 | private static final boolean hasInternet = ping(mi.mirrorListUrl()) != -1; 21 | 22 | @Test 23 | @Tag("IntegrationTest") 24 | void listAvailableMirrors() throws IOException 25 | { 26 | assumeTrue(hasInternet); 27 | System.out.println(mi.listAvailableMirrors()); 28 | mi.listAvailableMirrors().forEach(it -> System.out.println(it.url())); 29 | } 30 | 31 | @Test 32 | @Tag("IntegrationTest") 33 | void updateMirrors() throws IOException 34 | { 35 | assumeTrue(hasInternet); 36 | mi.updateMirrors(); 37 | } 38 | 39 | @Test 40 | @Tag("IntegrationTest") 41 | void pingMirrors() throws IOException 42 | { 43 | assumeTrue(hasInternet); 44 | var pings = mi.pingMirrors(); 45 | System.out.println(pings); 46 | for (int i = 0; i < pings.size() - 1; i++) 47 | { 48 | assertTrue(pings.get(i).v() <= pings.get(i + 1).v()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/database/MockPluginTracker.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database; 2 | 3 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 4 | import org.hydev.mcpm.client.models.PluginTrackerModel; 5 | import org.hydev.mcpm.client.models.PluginYml; 6 | import org.hydev.mcpm.utils.Pair; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /** 12 | * Mock implementation of the PluginTracker interface. 13 | * 14 | * @param entries A list of all entries in the lockfile. 15 | * @param installed A list of all "Installed" PluginYml files (to be returned in listInstalled). 16 | */ 17 | public record MockPluginTracker( 18 | Map entries, 19 | List installed 20 | ) implements PluginTracker { 21 | private static Map makeEntries(List installed) { 22 | return installed.stream() 23 | .map(x -> Pair.of(x.name(), true)) 24 | .collect(Pair.toMap()); 25 | } 26 | 27 | public MockPluginTracker(List installed) { 28 | this(makeEntries(installed), installed); 29 | } 30 | 31 | 32 | @Override 33 | public void addEntry(String name, boolean status, long versionId, long pluginId) { 34 | entries.put(name, status); 35 | } 36 | 37 | @Override 38 | public void removeEntry(String name) { 39 | entries.remove(name); 40 | } 41 | 42 | @Override 43 | public List listEntries() { 44 | return null; 45 | } 46 | 47 | @Override 48 | public List listInstalled() { 49 | return installed; 50 | } 51 | 52 | @Override 53 | public List listManuallyInstalled() { 54 | return entries().entrySet().stream() 55 | .filter(Map.Entry::getValue) 56 | .map(Map.Entry::getKey) 57 | .toList(); 58 | } 59 | 60 | @Override 61 | public List listOrphanPlugins(boolean considerSoftDependencies) { 62 | return List.of(); 63 | } 64 | 65 | @Override 66 | public Boolean findIfInLockByName(String name) { 67 | return entries.containsKey(name); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/database/SilentUpdatePresenter.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.database; 2 | 3 | import org.hydev.mcpm.client.commands.presenters.UpdatePresenter; 4 | import org.hydev.mcpm.client.updater.UpdateInput; 5 | import org.hydev.mcpm.client.updater.UpdateResult; 6 | 7 | /** 8 | * Update presenter implementation that does nothing when invoked. 9 | * Primarily used for testing, but can be used by other classes to discard presentation information. 10 | */ 11 | public class SilentUpdatePresenter implements UpdatePresenter { 12 | @Override 13 | public void present(UpdateInput input, UpdateResult result) { 14 | /* do nothing */ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/export/ExportInteractorTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.export; 2 | 3 | import org.hydev.mcpm.client.commands.controllers.ExportController; 4 | import org.hydev.mcpm.client.database.PluginMockFactory; 5 | import org.hydev.mcpm.client.database.tracker.MockPluginTracker; 6 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Tag; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.ArrayList; 12 | 13 | class ExportInteractorTest { 14 | static ExportInteractor interactor; 15 | static PluginTracker pt; 16 | 17 | @BeforeAll 18 | static void init() 19 | { 20 | var plugins = new ArrayList<>(PluginMockFactory.generateTestPlugins()); 21 | plugins.remove(0); // remove first plugin with no version 22 | 23 | pt = new MockPluginTracker(plugins); 24 | 25 | interactor = new ExportInteractor(pt); 26 | } 27 | 28 | @Test 29 | @Tag("IntegrationTest") 30 | void testExportImport() { 31 | var exportController = new ExportController(interactor, (exportPluginsResult, log) -> {}); 32 | exportController.export(new ExportPluginsInput("literal", ""), System.out::println); 33 | } 34 | } -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/installer/MockDownloader.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer; 2 | 3 | /** 4 | * Mock implementation of the PluginDownloader interface. 5 | * 6 | */ 7 | public class MockDownloader implements PluginDownloader { 8 | public void download(String pluginName, long pluginId, long pluginVersion) { 9 | System.out.println("Download completed!"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/client/installer/MockLocalPluginTracker.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.client.installer; 2 | 3 | import org.hydev.mcpm.client.database.PluginMockFactory; 4 | import org.hydev.mcpm.client.database.tracker.PluginTracker; 5 | import org.hydev.mcpm.client.models.PluginTrackerModel; 6 | import org.hydev.mcpm.client.models.PluginYml; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Mock implementation of the PluginTracker interface. 15 | * 16 | */ 17 | public class MockLocalPluginTracker implements PluginTracker { 18 | private final Map localJarFilesTracker; 19 | private final List pluginInstalled; 20 | 21 | public MockLocalPluginTracker() { 22 | this.localJarFilesTracker = new HashMap<>(); 23 | this.pluginInstalled = new ArrayList<>(); 24 | } 25 | 26 | /** 27 | * status: true - manual, false - auto 28 | * 29 | */ 30 | @Override 31 | public void addEntry(String name, boolean status, long versionId, long pluginId) { 32 | localJarFilesTracker.put(name, false); 33 | pluginInstalled.add(PluginMockFactory.meta(name, String.valueOf(versionId), "")); 34 | } 35 | 36 | @Override 37 | public void removeEntry(String name) { 38 | localJarFilesTracker.remove(name); 39 | for (PluginYml pluginYml : pluginInstalled) { 40 | if (pluginYml.name().equals(name)) { 41 | listInstalled().remove(pluginYml); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | public List listEntries() { 48 | return null; 49 | } 50 | 51 | @Override 52 | public List listInstalled() { 53 | return pluginInstalled; 54 | } 55 | 56 | @Override 57 | public Boolean findIfInLockByName(String name) { 58 | return localJarFilesTracker.containsKey(name); 59 | } 60 | 61 | @Override 62 | public List listManuallyInstalled() { 63 | return localJarFilesTracker.entrySet().stream() 64 | .filter(Map.Entry::getValue) 65 | .map(Map.Entry::getKey) 66 | .toList(); 67 | } 68 | 69 | @Override 70 | public List listOrphanPlugins(boolean considerSoftDependencies) { 71 | return null; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/ColorLoggerTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hydev.mcpm.utils.ColorLogger.printc; 7 | import static org.hydev.mcpm.utils.ColorLogger.printfc; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | /** 11 | * Tests for the ColorLogger class. 12 | */ 13 | class ColorLoggerTest 14 | { 15 | @Test 16 | @Tag("IntegrationTest") 17 | void toStdOut() 18 | { 19 | var log = ColorLogger.toStdOut(); 20 | log.accept("&aGreen! &cRed! &bBlue! &rDefault!"); 21 | printc("&aPrintc!"); 22 | printfc("&aPrintfc! %.2f", 0.031f); 23 | } 24 | 25 | @Test 26 | @Tag("IntegrationTest") 27 | void lengthNoColor() 28 | { 29 | assertEquals(ColorLogger.lengthNoColor("&bBlue!"), "Blue!".length()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/ConsoleUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | /** 7 | * Tests for ConsoleUtils 8 | */ 9 | class ConsoleUtilsTest 10 | { 11 | @Test 12 | @Tag("IntegrationTest") 13 | void move() 14 | { 15 | // NOTE: You must be on a Xterm-compatible TTY for this test to work, which is why it's 16 | // impossible to unit-test, and it can't even show up properly on gradle 17 | var cu = new ConsoleUtils(); 18 | cu.clear(); 19 | System.out.println("Hello World"); 20 | 21 | // This will print "Meow" 22 | System.out.println("Hello Hell"); 23 | cu.curUp(1); 24 | cu.eraseLine(); 25 | System.out.println("Meow"); 26 | 27 | // This will print "Hello Cell" 28 | System.out.println("Hello Hell"); 29 | cu.curUp(1); 30 | System.out.println("Hello C"); 31 | 32 | // This will print "Erased" 33 | System.out.print("This line is erased"); 34 | cu.eraseLine(); 35 | System.out.println("Erased"); 36 | } 37 | 38 | public static void main(String[] args) 39 | { 40 | new ConsoleUtilsTest().move(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/GeneralUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertNotNull; 8 | import static org.junit.jupiter.api.Assertions.assertTrue; 9 | 10 | /** 11 | * Tests for org.hydev.mcpm.utils.GeneralUtils 12 | */ 13 | class GeneralUtilsTest 14 | { 15 | @Test 16 | void makeUrl() 17 | { 18 | assertEquals(GeneralUtils.makeUrl("https://example.com", "cat", "meow", "azalea", "cute").toString(), 19 | "https://example.com?cat=meow&azalea=cute"); 20 | } 21 | 22 | @Test 23 | @Tag("IntegrationTest") 24 | void safeSleep() 25 | { 26 | long time = System.currentTimeMillis(); 27 | GeneralUtils.safeSleep(50); 28 | long elapsed = System.currentTimeMillis() - time; 29 | assertTrue((40 <= elapsed) && (elapsed <= 60)); 30 | } 31 | 32 | @Test 33 | void getResourceFile() 34 | { 35 | var absPath = GeneralUtils.getResourceFile("test-plugin-activelist.jar"); 36 | assertNotNull(absPath); 37 | assertTrue(absPath.isFile()); 38 | assertTrue(absPath.toString().endsWith("test-plugin-activelist.jar")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/HashUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | import java.security.NoSuchAlgorithmException; 7 | import java.util.Objects; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | 12 | class HashUtilsTest 13 | { 14 | private static final HashUtils hashUtils = new HashUtils(); 15 | 16 | @Test 17 | void hashString() 18 | { 19 | assertEquals(hashUtils.hash("hello world"), "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"); 20 | } 21 | 22 | @Test 23 | void hashFile() throws IOException 24 | { 25 | assertEquals(hashUtils.hash(Objects.requireNonNull(GeneralUtils.getResourceFile("test-plugin-activelist.jar"))), 26 | "6b51b4a80419843f522f0a612288c7f50cf405f5ab7dd9bb1d050cc6e80a725f"); 27 | } 28 | 29 | @Test 30 | void constructorFailTest() 31 | { 32 | assertThrows(NoSuchAlgorithmException.class, () -> new HashUtils("a nonexistant algorithm")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/NetworkUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | /** 9 | * Tests for NetworkUtils 10 | */ 11 | class NetworkUtilsTest 12 | { 13 | @Test 14 | @Tag("IntegrationTest") 15 | void ping() 16 | { 17 | // Since we might run the test in an offline environment, we shouldn't assert it has connectivity 18 | var ping = NetworkUtils.ping("1.1.1.1"); 19 | System.out.println("Ping to 1.1.1.1 is: " + ping + " ms"); 20 | 21 | assertTrue(ping >= 0); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/PairTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | /** 12 | * Tests for Pair class 13 | */ 14 | class PairTest 15 | { 16 | @Test 17 | void getters() 18 | { 19 | var p = Pair.of("a", 1); 20 | 21 | assertEquals(p.k(), p.getKey()); 22 | assertEquals(p.k(), "a"); 23 | assertEquals(p.v(), p.getValue()); 24 | assertEquals(p.v(), 1); 25 | } 26 | 27 | @Test 28 | void setterFail() 29 | { 30 | var p = Pair.of("a", 1); 31 | assertThrows(Pair.UnmodifiableException.class, () -> p.setValue(1)); 32 | } 33 | 34 | @Test 35 | void toMap() 36 | { 37 | var ps = List.of(Pair.of("a", 1), Pair.of("b", 2)); 38 | var map = ps.stream().collect(Pair.toMap()); 39 | 40 | assertTrue(map.containsKey("a") && map.containsKey("b")); 41 | assertEquals(map.get("a"), 1); 42 | assertEquals(map.get("b"), 2); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/PluginJarFileTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.hydev.mcpm.client.models.PluginYml; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.stream.Stream; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | import static org.junit.jupiter.api.Assertions.assertTrue; 14 | 15 | /** 16 | * Tests for org.hydev.mcpm.utils.PluginJarFile 17 | */ 18 | class PluginJarFileTest 19 | { 20 | PluginJarFile jar; 21 | 22 | @BeforeEach 23 | void setUp() throws IOException 24 | { 25 | // Open our test plugin 26 | jar = new PluginJarFile(Objects.requireNonNull(GeneralUtils.getResourceFile("test-plugin-activelist.jar"))); 27 | } 28 | 29 | @Test 30 | void readString() throws IOException 31 | { 32 | // Test reading the first line of plugin.yml 33 | assertEquals(jar.readString("plugin.yml").split("\n")[0].strip(), "name: ActiveList"); 34 | } 35 | 36 | @Test 37 | void readPluginYaml() throws IOException, PluginYml.InvalidPluginMetaStructure 38 | { 39 | var meta = jar.readPluginYaml(); 40 | 41 | assertEquals(meta.main(), "org.hydev.mc.ActiveList"); 42 | assertEquals(meta.name(), "ActiveList"); 43 | assertEquals(meta.version(), "1.1"); 44 | assertEquals(meta.getFirstAuthor(), "Hykilpikonna"); 45 | assertTrue(meta.commands().containsKey("activelist")); 46 | assertEquals(meta.commands().get("activelist").aliases(), List.of("al", "ll")); 47 | 48 | // Obtain all command names and aliases of a plugin meta 49 | var cmds = Stream.concat(meta.commands().values().stream().flatMap(c -> c.aliases().stream()), 50 | meta.commands().keySet().stream()).toList(); 51 | System.out.println(cmds); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/ReflectionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import com.google.common.reflect.TypeToken; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | /** 14 | * This class provides tests for the reflection related tools we use when hot reloading plugins. 15 | */ 16 | class ReflectionUtilsTest 17 | { 18 | private static class A 19 | { 20 | @SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection"}) 21 | private final List field = new ArrayList<>(List.of("meow", "qwq")); 22 | } 23 | 24 | @Test 25 | void getPrivateField() 26 | { 27 | var a = new A(); 28 | var f = ReflectionUtils.getPrivateField(a, "field", new TypeToken>(){}).orElseThrow(); 29 | assertEquals(f, Arrays.asList("meow", "qwq")); 30 | } 31 | 32 | @Test 33 | void getPrivateFieldMutation() 34 | { 35 | // Show that the obtained field is a pointer, and its contents can be mutated 36 | var a = new A(); 37 | var f = ReflectionUtils.getPrivateField(a, "field", new TypeToken>(){}).orElseThrow(); 38 | f.set(1, "wolf"); 39 | 40 | var newF = ReflectionUtils.getPrivateField(a, "field", new TypeToken>(){}).orElseThrow(); 41 | assertEquals(newF.get(1), "wolf"); 42 | } 43 | 44 | @Test 45 | void setPrivateField() 46 | { 47 | var a = new A(); 48 | var f = ReflectionUtils.getPrivateField(a, "field", new TypeToken>(){}).orElseThrow(); 49 | assertEquals(f.get(0), "meow"); 50 | assertTrue(ReflectionUtils.setPrivateField(a, "field", List.of("wolf"))); 51 | assertEquals(f.get(0), "meow"); 52 | 53 | var newF = ReflectionUtils.getPrivateField(a, "field", new TypeToken>(){}).orElseThrow(); 54 | assertEquals(newF.get(0), "wolf"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/StoredHashMapTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertFalse; 10 | 11 | class StoredHashMapTest 12 | { 13 | @Test 14 | @Tag("IntegrationTest") 15 | void putRemoveSave() 16 | { 17 | var fp = new File("test.meow.hashmap.json"); 18 | if (fp.exists()) { 19 | //noinspection ResultOfMethodCallIgnored 20 | fp.delete(); 21 | } 22 | 23 | var s = new StoredHashMap<>(fp); 24 | s.put("a", 1); 25 | s.put("b", 2); 26 | assertEquals(s.get("a"), 1); 27 | assertEquals(s.get("b"), 2); 28 | 29 | var s2 = new StoredHashMap<>(fp); 30 | assertEquals(s2.get("a"), 1); 31 | assertEquals(s2.get("b"), 2); 32 | s2.remove("a"); 33 | 34 | var s3 = new StoredHashMap<>(fp); 35 | assertFalse(s3.containsKey("a")); 36 | assertEquals(s3.get("b"), 2); 37 | s3.remove("b", 1); 38 | assertEquals(s3.get("b"), 2); 39 | s3.clear(); 40 | //noinspection ConstantConditions 41 | assertFalse(s3.containsKey("b")); 42 | 43 | //noinspection ResultOfMethodCallIgnored 44 | fp.delete(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/SugarTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | /** 12 | * Tests the various methods of the Sugar class. 13 | */ 14 | class SugarTest 15 | { 16 | @Test 17 | void sub() 18 | { 19 | var a = new Integer[] {1, 2, 3, 4, 5}; 20 | assertArrayEquals(Sugar.sub(a, 0, 5), a); 21 | assertArrayEquals(Sugar.sub(a, 0, 4), new Integer[] {1, 2, 3, 4}); 22 | assertArrayEquals(Sugar.sub(a, 1, 5), new Integer[] {2, 3, 4, 5}); 23 | assertArrayEquals(Sugar.sub(a, 1, 4), new Integer[] {2, 3, 4}); 24 | assertEquals(Sugar.sub(a, 5, 0).length, 0); 25 | 26 | // Negatives 27 | assertArrayEquals(Sugar.sub(a, -2, 5), new Integer[] {4, 5}); 28 | assertArrayEquals(Sugar.sub(a, -2, -1), new Integer[] {4}); 29 | assertArrayEquals(Sugar.sub(a, 0, -1), new Integer[] {1, 2, 3, 4}); 30 | } 31 | 32 | @Test 33 | void subFrom() 34 | { 35 | var a = new Integer[] {1, 2, 3, 4, 5}; 36 | assertArrayEquals(Sugar.subFrom(a, 0), a); 37 | assertArrayEquals(Sugar.subFrom(a, 1), new Integer[] {2, 3, 4, 5}); 38 | assertArrayEquals(Sugar.subFrom(a, -2), new Integer[] {4, 5}); 39 | assertEquals(Sugar.subFrom(a, 5).length, 0); 40 | assertEquals(Sugar.subFrom(a, 999).length, 0); 41 | } 42 | 43 | @Test 44 | void subTo() 45 | { 46 | var a = new Integer[] {1, 2, 3, 4, 5}; 47 | assertArrayEquals(Sugar.subTo(a, 5), a); 48 | assertArrayEquals(Sugar.subTo(a, 4), new Integer[] {1, 2, 3, 4}); 49 | assertArrayEquals(Sugar.subTo(a, -2), new Integer[] {1, 2, 3}); 50 | assertEquals(Sugar.subTo(a, -99).length, 0); 51 | } 52 | 53 | @Test 54 | void uncheckedMapOf() 55 | { 56 | Map map = Sugar.uncheckedMapOf("a", 1, "b", 2); 57 | assertTrue(map.containsKey("a") && map.containsKey("b")); 58 | assertEquals(map.get("a"), 1); 59 | assertEquals(map.get("b"), 2); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/TemporaryDirTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Tag; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Files; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertFalse; 12 | 13 | class TemporaryDirTest 14 | { 15 | @Test 16 | @Tag("IntegrationTest") 17 | void test() throws IOException 18 | { 19 | var t = new TemporaryDir(); 20 | var tf = new File(t.path, "test.txt").toPath(); 21 | Files.writeString(tf, "meow"); 22 | 23 | assertEquals(Files.readString(tf).strip(), "meow"); 24 | t.close(); 25 | System.out.println(tf.toFile()); 26 | assertFalse(tf.toFile().exists()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/UnitConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | /** 8 | * Tests for UnitConverter 9 | */ 10 | class UnitConverterTest 11 | { 12 | @Test 13 | void autoBinarySize() 14 | { 15 | assertEquals(UnitConverter.autoBinarySize(10).unit(), "B"); 16 | assertEquals(UnitConverter.autoBinarySize(1000_0).unit(), "KiB"); 17 | assertEquals(UnitConverter.autoBinarySize(1000_000_00d).unit(), "MiB"); 18 | assertEquals(UnitConverter.autoBinarySize(1000_000_000_0d).unit(), "GiB"); 19 | assertEquals(UnitConverter.autoBinarySize(1000_000_000_000_00d).unit(), "TiB"); 20 | } 21 | 22 | @Test 23 | void binarySpeedFormatter() 24 | { 25 | var fmt = UnitConverter.binarySpeedFormatter(); 26 | assertEquals(fmt.apply(10d), " 10.0 B/s"); 27 | assertEquals(fmt.apply(1000_0d), " 9.8 KiB/s"); 28 | assertEquals(fmt.apply(1000_000_00d), " 95.4 MiB/s"); 29 | assertEquals(fmt.apply(1000_000_000d), "953.7 MiB/s"); 30 | assertEquals(fmt.apply(1000_000_000_0d), " 9.3 GiB/s"); 31 | assertEquals(fmt.apply(1000_000_000_000_00d), " 90.9 TiB/s"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/hydev/mcpm/utils/arrays/SlidingWindowTest.java: -------------------------------------------------------------------------------- 1 | package org.hydev.mcpm.utils.arrays; 2 | 3 | import org.hydev.mcpm.utils.Pair; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Random; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class SlidingWindowTest { 12 | 13 | ArrayList> arr; 14 | SlidingWindow slidingWindow; 15 | long window; 16 | 17 | 18 | long naiveSum(long index) { 19 | long ret = 0; 20 | for (int i = arr.size() - 1; i >= 0; i--) { 21 | if (arr.get(i).getKey() < index - window) 22 | break; 23 | ret += arr.get(i).getValue(); 24 | } 25 | return ret; 26 | } 27 | 28 | @Test 29 | void testAll() { 30 | Random r = new Random(); 31 | for (int window = 1; window <= 5001; window += 5) { 32 | this.window = window; 33 | long cur = 0; 34 | arr = new ArrayList<>(); 35 | slidingWindow = new SlidingWindow(); 36 | slidingWindow.setWindowSize(window); 37 | for (int i = 0; i < 1000; i++) { 38 | long x = r.nextLong(); 39 | int ind = Math.abs(r.nextInt()); 40 | cur += ind; 41 | arr.add(new Pair<>(cur, x)); 42 | slidingWindow.add(cur, x); 43 | assertEquals(slidingWindow.sum(cur), naiveSum(cur)); 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/resources/db.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/src/test/resources/db.zst -------------------------------------------------------------------------------- /src/test/resources/test-plugin-activelist.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/src/test/resources/test-plugin-activelist.jar -------------------------------------------------------------------------------- /src/test/resources/testing.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/src/test/resources/testing.jar -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSC207-2022F-UofT/mcpm/30250ce66a3cd2eb2a5b036d6e1833077061d8bd/tools/__init__.py -------------------------------------------------------------------------------- /tools/analyze_file.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from collections import Counter 5 | from hypy_utils.tqdm_utils import pmap 6 | from pathlib import Path 7 | from subprocess import check_output 8 | 9 | 10 | def get_file(fp: Path): 11 | return check_output(['file', fp]).decode().split(":", 1)[1].strip() 12 | 13 | 14 | if __name__ == '__main__': 15 | base = Path(".mcpm/crawler/spiget/dl-cache/latest/") 16 | files = [base / f for f in os.listdir(base)] 17 | files = pmap(get_file, files) 18 | print("\n".join([f"{ty.ljust(100)} {count}" for ty, count in sorted(Counter(files).items(), key=lambda x: -x[1])])) 19 | -------------------------------------------------------------------------------- /tools/analyze_spiget.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import json 4 | import numpy as np 5 | from hypy_utils.scientific_utils import calc_col_stats 6 | from matplotlib import pyplot as plt 7 | from pathlib import Path 8 | 9 | if __name__ == '__main__': 10 | j = json.loads(Path(".mcpm/crawler/spiget/resources.json").read_text()) 11 | 12 | downloads = np.array([v['downloads'] for v in j]) 13 | # print(Counter(downlaods)) 14 | print(calc_col_stats(downloads)) 15 | 16 | # hist, bins, _ = plt.hist(downloads, bins=50, log=True) 17 | # plt.close() 18 | logbins = np.geomspace(1, max(downloads), 50) 19 | # logbins = np.logspace(np.log10(bins[0]), np.log10(bins[-1]), len(bins)) 20 | plt.hist(downloads, bins=logbins, log=True) 21 | plt.xscale('log') 22 | # plt.xlim(0, 8000) 23 | # plt.tick(style='plain') 24 | plt.xlabel("Number of downloads") 25 | plt.ylabel("Number of occurrences (for each histogram bin)") 26 | plt.title("Histogram for number of downloads for each plugin") 27 | plt.show() 28 | # plt.boxplot(downloads) 29 | # plt.show() 30 | -------------------------------------------------------------------------------- /tools/crawler/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) 3 | 4 | # Create links 5 | sudo ln -sf "$SCRIPT_DIR/mcprs-crawl.service" "/etc/systemd/system/mcprs-crawl.service" 6 | sudo ln -sf "$SCRIPT_DIR/mcprs-crawl.timer" "/etc/systemd/system/mcprs-crawl.timer" 7 | 8 | # Enable sctl 9 | sudo systemctl daemon-reload 10 | sudo systemctl enable mcprs-crawl.timer -------------------------------------------------------------------------------- /tools/crawler/mcprs-crawl.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Crawl MCPRS packages 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=bash /ws/mcpm/crawl.sh 7 | User=azalea 8 | Group=azalea 9 | 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /tools/crawler/mcprs-crawl.timer: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Timer for MCPRS cralwer 3 | Requires=mcprs-crawl.service 4 | 5 | [Timer] 6 | Unit=mcprs-crawl.service 7 | OnCalendar=*-*-* 5:00:00 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /tools/gen_pyml_deps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from pathlib import Path 3 | 4 | from ruamel.yaml import YAML 5 | 6 | 7 | def gen_pyml_deps(): 8 | """ 9 | Generate plugin.yml libraries from build.gradle 10 | """ 11 | ln = [l.strip() for l in Path("build.gradle").read_text().splitlines()] 12 | ln = [l.lstrip("implementation ").strip("'").strip('"') for l in ln if l.startswith("implementation ")] 13 | with open("src/main/resources/plugin.yml") as f: 14 | yml = YAML().load(f) 15 | yml["libraries"] = ln 16 | with open("src/main/resources/plugin.yml", "w") as f: 17 | YAML().dump(yml, f) 18 | 19 | 20 | if __name__ == '__main__': 21 | gen_pyml_deps() 22 | -------------------------------------------------------------------------------- /tools/mirror/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | mcprs-sync: 5 | container_name: mcprs-sync 6 | build: sync 7 | volumes: 8 | - /data/mcprs:/data:z 9 | environment: 10 | - RSYNC_SOURCE=rsync://mcprs-bell.hydev.org/mcprs 11 | - RSYNC_INTERVAL=24 12 | restart: always 13 | 14 | mcprs-nginx: 15 | container_name: mcprs-nginx 16 | build: nginx 17 | volumes: 18 | - /data/mcprs:/data:z 19 | ports: 20 | - "80:80" 21 | restart: always 22 | 23 | mcprs-rsyncd: 24 | container_name: mcprs-rsyncd 25 | image: axiom/rsync-server 26 | ports: 27 | - "873:873" 28 | volumes: 29 | - /data/mcprs:/data:z 30 | restart: always 31 | -------------------------------------------------------------------------------- /tools/mirror/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | # Copy nginx configuration 4 | COPY mcprs.conf /etc/nginx/templates/mcprs.conf.template 5 | -------------------------------------------------------------------------------- /tools/mirror/nginx/mcprs.conf: -------------------------------------------------------------------------------- 1 | # MCPRS HTTP 2 | server 3 | { 4 | listen 80 default_server; 5 | listen [::]:80 default_server; 6 | server_name default; 7 | 8 | root /data; 9 | 10 | location / { 11 | autoindex on; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tools/mirror/sync/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye-slim 2 | 3 | # Install dependencies 4 | RUN apt-get update && apt-get install -y \ 5 | rsync \ 6 | python3-schedule \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | # Copy clone script 10 | COPY sync.py /app/sync.py 11 | 12 | CMD ["python3", "-u", "/app/sync.py"] 13 | -------------------------------------------------------------------------------- /tools/mirror/sync/sync.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import time 4 | from subprocess import Popen 5 | 6 | import schedule 7 | 8 | RSYNC_ARGS = shlex.split("-rlptH --info=progress2 --safe-links --delete-delay --delay-updates " 9 | "--timeout=600 --contimeout=60 --no-motd") 10 | 11 | 12 | if __name__ == '__main__': 13 | print("Mirror starting...") 14 | 15 | # Check environment variables 16 | SOURCE = os.environ.get("RSYNC_SOURCE") 17 | INTERVAL = os.environ.get("RSYNC_INTERVAL") 18 | assert SOURCE is not None, "Please set RSYNC_SOURCE environment variable: " \ 19 | "The source you want to sync from." 20 | assert INTERVAL is not None, "Please set RSYNC_INTERVAL environment variable: " \ 21 | "How many hours should rsync wait between updates." 22 | INTERVAL = int(INTERVAL) 23 | assert INTERVAL >= 6, f"Error: RSYNC_INTERVAL should not be set to {INTERVAL} < 6 hours. " \ 24 | f"Please don't update that frequently, it occupies a lot of server bandwidth" 25 | 26 | # Optional environment variables 27 | EXTRA_ARGS = os.environ.get("RSYNC_EXTRA_ARGS") or "" 28 | 29 | # Check write permission 30 | assert os.access('/data', os.W_OK), "/data is not writable. Please check your docker volume config.\n" \ 31 | "If SELinux is enabled, you might need to set proper permissions or disable it." 32 | 33 | print(f"Mirror Initialized! Sync from {SOURCE} to /data every {INTERVAL} hours") 34 | 35 | # Schedule task 36 | def rsync(): 37 | cmd = ["rsync", *RSYNC_ARGS, SOURCE, *shlex.split(EXTRA_ARGS), "/data"] 38 | print(f"Starting rsync with {' '.join(cmd)}") 39 | Popen(cmd).wait() 40 | print("rsync finished.") 41 | 42 | schedule.every(INTERVAL).hours.do(rsync).run() 43 | 44 | print("Starting scheduler...") 45 | 46 | while 1: 47 | schedule.run_pending() 48 | time.sleep(10) 49 | 50 | -------------------------------------------------------------------------------- /tools/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Use this with python3 -m tools.run 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import subprocess 7 | from subprocess import run, check_call 8 | 9 | from . import download_jdk 10 | 11 | ALIAS = { 12 | "Entry": "org.hydev.mcpm.client.arguments.ArgsParser", 13 | "ProgressBar": "org.hydev.mcpm.client.interaction.ProgressBar", 14 | "ConsoleUtilsTest": "org.hydev.mcpm.utils.ConsoleUtilsTest" 15 | } 16 | 17 | if __name__ == '__main__': 18 | a = argparse.ArgumentParser("run.py", "Java Gradle external runner") 19 | a.add_argument('classname', help="Full class name (e.g. org.hydev.mcpm.client.Launcher) or alias (e.g. Launcher)", nargs="?", 20 | default='Entry') 21 | a.add_argument('-a', '--args', help="Arguments passed into the program", nargs="+") 22 | a.add_argument('-j', '--java', help="Custom java path") 23 | args = a.parse_args() 24 | cls = args.classname 25 | 26 | # Replace alias 27 | if cls in ALIAS: 28 | cls = ALIAS[cls] 29 | 30 | # Download JDK 31 | java = args.java or download_jdk.ensure_java("19") 32 | 33 | # Build classes 34 | check_call("./gradlew classes testClasses", shell=True) 35 | 36 | # Get java runtime classpath 37 | p = run('./gradlew printCp', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 38 | cp = p.stderr.decode().strip() 39 | 40 | # Run java 41 | try: 42 | cmd = [java, "-cp", cp, cls, *(args.args or [])] 43 | check_call(cmd) 44 | except subprocess.CalledProcessError: 45 | pass 46 | 47 | 48 | -------------------------------------------------------------------------------- /tools/update_build.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | from tools.start_server import update_build 6 | 7 | if __name__ == '__main__': 8 | mc_path = Path('build/mc-server') 9 | update_build(mc_path) 10 | --------------------------------------------------------------------------------