From af4634d497dc1a87d1c1f659db2eb483704f5d66 Mon Sep 17 00:00:00 2001 From: Matt Date: Fri, 8 May 2020 20:08:31 +0200 Subject: [PATCH] Technical Update (v0.6.0) (#17) # Introductory notes - **This update is recommended because it contains many improvements and bug fixes.** - The update is backward compatible with **v0.5.x** - If you are already using BedcoreProtect **v0.5.x** and you upgrade to **v0.6.0, you will notice a message of database upgrading**. Don't worry, that's normal. It's the patch manager process that updates the database tables for the new version. **If during the process you got a crash, please open a issue [here](https://github.com/matcracker/BedcoreProtect/issues/new/choose)!** # Changelog (Last update: 08/05/2020) The update is not finished yet and the changelog could be updated in any time. ## Features - Added /bcp undo command _(Permission: bcp.subcommand.undo)_. Allows to undo the last rollback/restore. - Now block restrictions works. So, is now possible to include/exclude certain blocks during a rollback or a lookup. - When a player spawn an entity through a spawn egg is now logged. - When a entity dies due to a block (e.g. fire, lava, etc...) is now logged. - When a player ignites a TNT is now logged. - Code analyzed with PHPStan level 8. ## Improvements - With SQLite the data will be saved every 5 minutes (same as worlds) instead of 5 hours. - Re-wrote query execution code getting a speed up! - Most of queries when executed are now scheduled in order by preventing missing or wrong logs. - Improved the QueryGenerator execution. - Improved painting break tracking. - Removed tiles when rollback placed blocks. - General minor improvements. ## Changes - The patch manager is now able to patch a different numbers of patches for each database type. - From now in the database version of status command (/bcp status) will be applied a suffix indicating if the current database version is backward compatible. _(e.g. - Database version: v0.6.1 (Backward Compatible))_ - Default SQLite database file name is now **logs.db**. (This change is backward compatible) ## Bug fixes - Fixed #9 - Fixed #12 - Fixed an issue not allowing inspector to getting logs of the replaced block. - Fixed crash during the rollback of entities. - Fixed possible issue with command permission check order. - Fixed order of plugin data folder creation. - Fixed wrong position calculation of liquid tracking. - Fixed correct meta value (damage) when placing block. - Fixed wrong restore of bed and doors when placed. - Fixed duplicated door log when breaking. - Fixed block burning rollback not correctly working some times. - Fixed purge command not correctly working on SQLite. - Fixed excessive log during inventory transaction. - Fixed database file creation on start-up not taking the choosed file name in the config. - Fixed possibles crashes relative to null values while tracking. - When rollback an area containing blocks with light source, the light will try to be updated. (This avoid empty light areas) - Don't log anymore players when they die. It causes a crash while rollback. --- .gitignore | 7 +- .poggit.yml | 12 +- README.md | 2 + phpstan.neon.dist | 20 ++ plugin.yml | 27 +- resources/config.yml | 6 +- resources/languages/eng.ini | 7 + resources/languages/ita.ini | 7 + resources/{bedcore_database.db => logs.db} | Bin resources/mysql.sql | 54 ++-- resources/patches/.patches | 8 + resources/patches/mysql_patch.sql | 14 + resources/patches/sqlite_patch.sql | 93 ++++++ resources/sqlite.sql | 55 ++-- src/matcracker/BedcoreProtect/Inspector.php | 16 +- src/matcracker/BedcoreProtect/Main.php | 103 ++++--- .../BedcoreProtect/commands/BCPCommand.php | 168 +++++----- .../commands/BCPHelpCommand.php | 118 +++---- .../BedcoreProtect/commands/CommandParser.php | 159 +++++----- .../BedcoreProtect/enums/Action.php | 3 + .../listeners/BedcoreListener.php | 30 +- .../listeners/BlockListener.php | 150 ++++----- .../listeners/BlockSniperListener.php | 17 +- .../listeners/EntityListener.php | 83 +++-- .../listeners/InspectorListener.php | 116 +++++++ .../listeners/PlayerListener.php | 153 +++++----- .../listeners/WorldListener.php | 8 +- src/matcracker/BedcoreProtect/math/Area.php | 22 +- .../AbstractSerializable.php} | 20 +- .../serializable/SerializableBlock.php | 54 ++-- .../serializable/SerializableEntity.php | 138 +++++++++ .../serializable/SerializableItem.php | 37 ++- ...ableWorld.php => SerializablePosition.php} | 53 ++-- .../BedcoreProtect/storage/Database.php | 65 ++-- .../BedcoreProtect/storage/PatchManager.php | 96 ++++-- .../BedcoreProtect/storage/QueryManager.php | 263 ++++++++++++++++ .../storage/queries/BlocksQueries.php | 254 +++++++++++++++ .../storage/queries/EntitiesQueries.php | 186 +++++++++++ .../storage/queries/InventoriesQueries.php | 200 ++++++++++++ .../storage/queries/PluginQueries.php | 144 +++++++++ .../storage/queries/Queries.php | 203 ------------ .../storage/queries/QueriesBlocksTrait.php | 289 ------------------ .../storage/queries/QueriesConst.php | 4 +- .../storage/queries/QueriesEntitiesTrait.php | 106 ------- .../queries/QueriesInventoriesTrait.php | 133 -------- .../BedcoreProtect/storage/queries/Query.php | 156 ++++++++++ .../tasks/SQLiteTransactionTask.php | 20 +- ...rator.php => BlocksQueryGeneratorTask.php} | 38 +-- ....php => InventoriesQueryGeneratorTask.php} | 37 +-- ...nerator.php => LogsQueryGeneratorTask.php} | 40 +-- .../tasks/async/QueryGeneratorTask.php | 70 +++++ ...AsyncRollbackTask.php => RollbackTask.php} | 72 +++-- src/matcracker/BedcoreProtect/ui/Forms.php | 52 ++-- .../BedcoreProtect/utils/BlockUtils.php | 6 +- .../BedcoreProtect/utils/ConfigParser.php | 9 + .../BedcoreProtect/utils/EntityUtils.php | 102 +++++++ .../BedcoreProtect/utils/UndoRollbackData.php | 75 +++++ src/matcracker/BedcoreProtect/utils/Utils.php | 95 ++---- 58 files changed, 2854 insertions(+), 1621 deletions(-) create mode 100644 phpstan.neon.dist rename resources/{bedcore_database.db => logs.db} (100%) create mode 100644 src/matcracker/BedcoreProtect/listeners/InspectorListener.php rename src/matcracker/BedcoreProtect/{tasks/async/AsyncRestoreTask.php => serializable/AbstractSerializable.php} (61%) create mode 100644 src/matcracker/BedcoreProtect/serializable/SerializableEntity.php rename src/matcracker/BedcoreProtect/serializable/{SerializableWorld.php => SerializablePosition.php} (55%) create mode 100644 src/matcracker/BedcoreProtect/storage/QueryManager.php create mode 100644 src/matcracker/BedcoreProtect/storage/queries/BlocksQueries.php create mode 100644 src/matcracker/BedcoreProtect/storage/queries/EntitiesQueries.php create mode 100644 src/matcracker/BedcoreProtect/storage/queries/InventoriesQueries.php create mode 100644 src/matcracker/BedcoreProtect/storage/queries/PluginQueries.php delete mode 100644 src/matcracker/BedcoreProtect/storage/queries/Queries.php delete mode 100644 src/matcracker/BedcoreProtect/storage/queries/QueriesBlocksTrait.php delete mode 100644 src/matcracker/BedcoreProtect/storage/queries/QueriesEntitiesTrait.php delete mode 100644 src/matcracker/BedcoreProtect/storage/queries/QueriesInventoriesTrait.php create mode 100644 src/matcracker/BedcoreProtect/storage/queries/Query.php rename src/matcracker/BedcoreProtect/tasks/async/{AsyncBlocksQueryGenerator.php => BlocksQueryGeneratorTask.php} (69%) rename src/matcracker/BedcoreProtect/tasks/async/{AsyncInventoriesQueryGenerator.php => InventoriesQueryGeneratorTask.php} (53%) rename src/matcracker/BedcoreProtect/tasks/async/{AsyncLogsQueryGenerator.php => LogsQueryGeneratorTask.php} (58%) create mode 100644 src/matcracker/BedcoreProtect/tasks/async/QueryGeneratorTask.php rename src/matcracker/BedcoreProtect/tasks/async/{AsyncRollbackTask.php => RollbackTask.php} (58%) create mode 100644 src/matcracker/BedcoreProtect/utils/EntityUtils.php create mode 100644 src/matcracker/BedcoreProtect/utils/UndoRollbackData.php diff --git a/.gitignore b/.gitignore index 22b41500..d28b12e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /.idea -/src/poggit -/src/JackMD -/vendor/pocketmine \ No newline at end of file +/libs +/scripts +/.php_cs.cache +TODO \ No newline at end of file diff --git a/.poggit.yml b/.poggit.yml index 079ecebc..8b2c3baa 100644 --- a/.poggit.yml +++ b/.poggit.yml @@ -10,11 +10,11 @@ projects: icon: resources/bedcoreprotect_logo.png libs: - src: poggit/libasynql/libasynql - version: 3.2.0 + version: ~3.3.0 + - src: SOF3/await-generator/await-generator + version: ~2.3.0 - src: JackMD/UpdateNotifier/UpdateNotifier - version: 1.0.0 + version: ~2.0.0 - src: matcracker/FormLib/FormLib - version: 1.1.0 - - src: SOF3/await-generator/await-generator - version: 2.2.0 -... + version: ~1.1.0 +... \ No newline at end of file diff --git a/README.md b/README.md index c124260d..5d05c0a2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![](https://poggit.pmmp.io/shield.dl.total/BedcoreProtect)](https://poggit.pmmp.io/p/BedcoreProtect) [![](https://poggit.pmmp.io/shield.state/BedcoreProtect)](https://poggit.pmmp.io/p/BedcoreProtect) [![](https://poggit.pmmp.io/shield.api/BedcoreProtect)](https://poggit.pmmp.io/p/BedcoreProtect) [![Discord](https://img.shields.io/discord/620519017148579841.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/eAePyeb) @@ -79,6 +80,7 @@ The main command is **/bedcoreprotect** but it accepts the folllowing aliases: * **Shortcut commands:** - **/bcp near \[value]**: _Performs a lookup with radius (default 5)_ (**Permission:** _bcp.subcommand.near_) +- **/bcp undo**: _Revert a rollback/restore via the opposite action_ (**Permission:** _bcp.subcommand.undo_) --- **Advanced command overview:** > **/bcp help**
diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..f5ae3fc3 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,20 @@ +parameters: + level: max + checkMissingIterableValueType: false + paths: + - /source/src + autoload_files: + - phar:///pocketmine/PocketMine-MP.phar/vendor/autoload.php + autoload_directories: + - /source/src + - /deps + ignoreErrors: + - + message: "#^Method matcracker\\\\BedcoreProtect\\\\enums\\\\Action\\:\\:getAll\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: /source/src/matcracker/BedcoreProtect/enums/Action.php + + - + message: "#^Method matcracker\\\\BedcoreProtect\\\\enums\\\\Action\\:\\:fromString\\(\\) should return matcracker\\\\BedcoreProtect\\\\enums\\\\Action but returns object\\.$#" + count: 1 + path: /source/src/matcracker/BedcoreProtect/enums/Action.php \ No newline at end of file diff --git a/plugin.yml b/plugin.yml index 9831e03e..29019023 100644 --- a/plugin.yml +++ b/plugin.yml @@ -1,11 +1,8 @@ --- name: BedcoreProtect main: matcracker\BedcoreProtect\Main -version: 0.5.7 +version: 0.6.0 api: [3.4.0] -mcpe-protcol: - - 354 - - 361 softdepend: BlockSniper load: POSTWORLD @@ -19,22 +16,24 @@ permissions: children: bcp.subcommand.help: description: "Allows the user to run the sub-command /bedcoreprotect help" - bcp.subcommand.reload: - description: "Allows the user to run the sub-command /bedcoreprotect reload" - bcp.subcommand.status: - description: "Allows the user to run the sub-command /bedcoreprotect status" bcp.subcommand.inspect: description: "Allows the user to run the sub-command /bedcoreprotect inspect" - bcp.subcommand.near: - description: "Allows the user to run the sub-command /bedcoreprotect near" bcp.subcommand.lookup: description: "Allows the user to run the sub-command /bedcoreprotect lookup" + bcp.subcommand.menu: + description: "Allows the user to run the sub-command /bedcoreprotect menu" + bcp.subcommand.near: + description: "Allows the user to run the sub-command /bedcoreprotect near" + bcp.subcommand.purge: + description: "Allows the user to run the sub-command /bedcoreprotect purge" bcp.subcommand.rollback: description: "Allows the user to run the sub-command /bedcoreprotect rollback" + bcp.subcommand.reload: + description: "Allows the user to run the sub-command /bedcoreprotect reload" bcp.subcommand.restore: description: "Allows the user to run the sub-command /bedcoreprotect restore" - bcp.subcommand.purge: - description: "Allows the user to run the sub-command /bedcoreprotect purge" - bcp.subcommand.menu: - description: "Allows the user to run the sub-command /bedcoreprotect menu" + bcp.subcommand.status: + description: "Allows the user to run the sub-command /bedcoreprotect status" + bcp.subcommand.undo: + description: "Allows the user to run the sub-command /bedcoreprotect undo" ... \ No newline at end of file diff --git a/resources/config.yml b/resources/config.yml index d1e27d5b..c7b3f2f3 100644 --- a/resources/config.yml +++ b/resources/config.yml @@ -1,3 +1,4 @@ +--- # Plugin language language: eng @@ -10,7 +11,7 @@ database: sqlite: # The file name of the database in the plugin data folder. # You can also put an absolute path here. - file: bedcore_database.db + file: logs.db # Edit these settings only if you choose "mysql". mysql: host: 127.0.0.1 @@ -18,7 +19,7 @@ database: username: root password: "" # The database name where plugin stores data. - schema: + schema: "" # The maximum number of simultaneous SQL queries # Recommended: 1 for sqlite, 2 for MySQL. You may want to further increase this value if your MySQL connection is very slow. worker-limit: 1 @@ -117,3 +118,4 @@ player-interactions: true # Logs changes made via the plugin "BlockSniper" if it's in use on your server. blocksniper-hook: false +... \ No newline at end of file diff --git a/resources/languages/eng.ini b/resources/languages/eng.ini index 2da33f00..142c3f0f 100644 --- a/resources/languages/eng.ini +++ b/resources/languages/eng.ini @@ -11,6 +11,7 @@ command.usage = Usage: /bcp help to display commands list command.no-permission = You don't have permission to run this command. command.reload.success = Plugin configuration reloaded. command.reload.no-success = Plugin configuration not reloaded due to some errors. Check your console to see the errors. Until you fix them, it will be used the old configuration. +command.status.bc = Backward-Compatible command.status.version = Version: §f{%0} command.status.database-connection = Database connection: §f{%0} command.status.database-version = Database version: §f{%0} @@ -65,7 +66,9 @@ command.help.examples = Examples command.help.shortcut = Command shortcut. command.help.block-names = Block names command.help.info-not-found = Information for command "/bcp help {%0}" not found. +command.undo.not-found = No previous rollback/restore found. database.version.higher = Your database is running a higher version than BedcoreProtect. Please update the plugin if you want to use it. +database.version.upgrading = Upgrading your database to v{%0}, please wait... database.version.updated = Your database is now updated from v{%0} to v{%1}. database.connection.fail = Could not connect to the database! Check your connection, database settings or plugin configuration file. form.menu.title = Main Menu @@ -88,6 +91,7 @@ form.input-menu.exclude-blocks = Exclude blocks (accepts ID:meta) general.action = Action general.rollback = Rollback general.restore = Restore +general.undo = Undo general.lookup = Lookup generic.yes = Yes generic.no = No @@ -95,6 +99,7 @@ inspector.no-data = No block data found for this location. inspector.more-lines = The lines number must be greater than 1. inspector.page-not-exist = The page §6{%0}§c does not exist! inspector.page = Page {%0}/{%1} +inspector.corrupted-data = The current log data may be damaged due to some database operations, please try again later. In the event of a continuous error, please report the problem. inspector.view-old-data = View older data by typing language.name = English parser.few-many-parameters = You are using too few or too many parameters (Max: {%0}) @@ -108,9 +113,11 @@ parser.invalid-action = Please specify a valid action. parser.invalid-block-include = Invalid block "{%0}" to include. parser.invalid-block-exclude = Invalid block "{%0}" to exclude. parser.missing-parameters = You are missing one of the following parameters: {%0} +rollback.report = Report rollback.completed = Rollback completed for {%0}. rollback.date = Rolled back {%0}. rollback.radius = Radius: {%0} block(s). +rollback.no-changes = No changes have been made. rollback.blocks = Approx. {%0} block(s) changed. rollback.items = Approx. {%0} item(s) changed. rollback.entities = Approx. {%0} entities(s) changed. diff --git a/resources/languages/ita.ini b/resources/languages/ita.ini index a04c41f2..8c94edf9 100644 --- a/resources/languages/ita.ini +++ b/resources/languages/ita.ini @@ -11,6 +11,7 @@ command.usage = Usa: /bcp help per mostrare la lista di comandi command.no-permission = Non hai il permesso di eseguire questo comando. command.reload.success = Configurazione del plugin ricaricate. command.reload.no-success = Configurazione del plugin non ricaricata a causa di alcuni errori. Controlla la console per vederli. Finchè non li sistemerai, verrà usata la vecchia configurazione. +command.status.bc = Retrocompatibile command.status.version = Versione: §f{%0} command.status.database-connection = Connessione database: §f{%0} command.status.database-version = Versione database: §f{%0} @@ -65,7 +66,9 @@ command.help.examples = Esempi command.help.shortcut = Scorciatoia del comando. command.help.block-names = Nomi dei blocchi command.help.info-not-found = Informazioni per il comando "/bcp help {%0}" non trovate. +command.undo.not-found = Non è stato trovato nessun rollback/ripristino precedente. database.version.higher = Il tuo database sta eseguendo una versione più aggiornata di BedcoreProtect. Per favore aggiorna il tuo plugin se vuoi usarlo. +database.version.upgrading = Aggiornamento del database alla v{%0}, attendere prego... database.version.updated = Il tuo database è stato aggiornato dalla versione v{%0} alla v{%1}. database.connection.fail = Impossibile connettersi al database! Controlla la tua connessione, impostazioni del database o la configurazione del plugin. form.menu.title = Menu principale @@ -88,6 +91,7 @@ form.input-menu.exclude-blocks = Escludi blocchi (accetta ID:meta) general.action = Azione general.rollback = Rollback general.restore = Ripristino +general.undo = Annulla general.lookup = Consulta generic.yes = Si generic.no = No @@ -95,6 +99,7 @@ inspector.no-data = Nessun dato trovato in questa posizione. inspector.more-lines = Il numero di linee deve essere più grande di 1. inspector.page-not-exist = La pagina §6{%0}§c non esiste! inspector.page = Pagina {%0}/{%1} +inspector.corrupted-data = I dati del registro corrente potrebbero essere danneggiati a causa di alcune operazioni del database, si prega di riprovare più tardi. In caso di errore continuo, si prega di segnalare il problema. inspector.view-old-data = Vedi i dati vecchi scrivendo language.name = Italiano parser.few-many-parameters = You are using too few or too many parameters (Max: {%0}) @@ -108,9 +113,11 @@ parser.invalid-action = Per favore specifica un'azione valida. parser.invalid-block-include = Blocco "{%0}" invalido da includere. parser.invalid-block-exclude = Blocco "{%0}" invalido da escludere. parser.missing-parameters = Stai dimenticando uno dei seguenti paramentri: {%0} +rollback.report = Rapporto rollback.completed = Rollback completato per {%0}. rollback.date = Rolled back {%0}. rollback.radius = Raggio: {%0} block(s). +rollback.no-changes = Non è stato fatto nessun cambiamento. rollback.blocks = Circa {%0} blocco/hi cambiato/i. rollback.items = Circa. {%0} oggetto/i cambiato/i. rollback.entities = Circa {%0} entità cambiate. diff --git a/resources/bedcore_database.db b/resources/logs.db similarity index 100% rename from resources/bedcore_database.db rename to resources/logs.db diff --git a/resources/mysql.sql b/resources/mysql.sql index 88622231..f05686bf 100644 --- a/resources/mysql.sql +++ b/resources/mysql.sql @@ -13,27 +13,27 @@ CREATE TABLE IF NOT EXISTS entities -- # {log_history CREATE TABLE IF NOT EXISTS log_history ( - log_id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY NOT NULL, - who VARCHAR(36) NOT NULL, - x BIGINT NOT NULL, - y TINYINT UNSIGNED NOT NULL, - z BIGINT NOT NULL, - world_name VARCHAR(255) NOT NULL, - action TINYINT UNSIGNED NOT NULL, - time TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL, - rollback BOOLEAN DEFAULT FALSE NOT NULL, + log_id BIGINT UNSIGNED UNIQUE AUTO_INCREMENT PRIMARY KEY NOT NULL, + who VARCHAR(36) NOT NULL, + x BIGINT NOT NULL, + y TINYINT UNSIGNED NOT NULL, + z BIGINT NOT NULL, + world_name VARCHAR(255) NOT NULL, + action TINYINT UNSIGNED NOT NULL, + time TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP NOT NULL, + rollback BOOLEAN DEFAULT FALSE NOT NULL, FOREIGN KEY (who) REFERENCES entities (uuid) ); -- # } -- # {blocks_log CREATE TABLE IF NOT EXISTS blocks_log ( - history_id BIGINT UNSIGNED NOT NULL, - old_id INTEGER UNSIGNED NOT NULL, - old_meta TINYINT(2) UNSIGNED NOT NULL, + history_id BIGINT UNSIGNED UNIQUE NOT NULL, + old_id INTEGER UNSIGNED NOT NULL, + old_meta TINYINT(2) UNSIGNED NOT NULL, old_nbt LONGBLOB DEFAULT NULL, - new_id INTEGER UNSIGNED NOT NULL, - new_meta TINYINT(2) UNSIGNED NOT NULL, + new_id INTEGER UNSIGNED NOT NULL, + new_meta TINYINT(2) UNSIGNED NOT NULL, new_nbt LONGBLOB DEFAULT NULL, FOREIGN KEY (history_id) REFERENCES log_history (log_id) ON DELETE CASCADE ); @@ -41,9 +41,9 @@ CREATE TABLE IF NOT EXISTS blocks_log -- # {entities_log CREATE TABLE IF NOT EXISTS entities_log ( - history_id BIGINT UNSIGNED NOT NULL, - entityfrom_uuid VARCHAR(36) NOT NULL, - entityfrom_id INTEGER UNSIGNED NOT NULL, + history_id BIGINT UNSIGNED UNIQUE NOT NULL, + entityfrom_uuid VARCHAR(36) NOT NULL, + entityfrom_id INTEGER UNSIGNED NOT NULL, entityfrom_nbt LONGBLOB DEFAULT NULL, FOREIGN KEY (history_id) REFERENCES log_history (log_id) ON DELETE CASCADE, FOREIGN KEY (entityfrom_uuid) REFERENCES entities (uuid) @@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS entities_log -- # {inventories_log CREATE TABLE IF NOT EXISTS inventories_log ( - history_id BIGINT UNSIGNED NOT NULL, + history_id BIGINT UNSIGNED UNIQUE NOT NULL, slot TINYINT UNSIGNED NOT NULL, old_id INTEGER UNSIGNED DEFAULT 0 NOT NULL, old_meta TINYINT(2) UNSIGNED DEFAULT 0 NOT NULL, @@ -104,6 +104,7 @@ INSERT INTO log_history(who, x, y, z, world_name, VALUES ((SELECT uuid FROM entities WHERE uuid = :uuid), :x, :y, :z, :world_name, :action); -- # } -- # {block +-- # :log_id int -- # :old_id int -- # :old_meta int -- # :old_nbt ?string @@ -112,17 +113,18 @@ VALUES ((SELECT uuid FROM entities WHERE uuid = :uuid), :x, :y, :z, :world_name, -- # :new_nbt ?string INSERT INTO blocks_log(history_id, old_id, old_meta, old_nbt, new_id, new_meta, new_nbt) -VALUES (LAST_INSERT_ID(), :old_id, :old_meta, :old_nbt, :new_id, :new_meta, - :new_nbt); +VALUES (:log_id, :old_id, :old_meta, :old_nbt, :new_id, :new_meta, :new_nbt); -- # } -- # {entity +-- # :log_id int -- # :uuid string -- # :id int -- # :nbt ?string INSERT INTO entities_log(history_id, entityfrom_uuid, entityfrom_id, entityfrom_nbt) -VALUES (LAST_INSERT_ID(), (SELECT uuid FROM entities WHERE uuid = :uuid), :id, :nbt); +VALUES (:log_id, (SELECT uuid FROM entities WHERE uuid = :uuid), :id, :nbt); -- # } -- # {inventory +-- # :log_id int -- # :slot int -- # :old_id int 0 -- # :old_meta int 0 @@ -134,8 +136,7 @@ VALUES (LAST_INSERT_ID(), (SELECT uuid FROM entities WHERE uuid = :uuid), :id, : -- # :new_amount int 0 INSERT INTO inventories_log(history_id, slot, old_id, old_meta, old_nbt, old_amount, new_id, new_meta, new_nbt, new_amount) -VALUES (LAST_INSERT_ID(), :slot, :old_id, :old_meta, :old_nbt, :old_amount, :new_id, - :new_meta, :new_nbt, :new_amount); +VALUES (:log_id, :slot, :old_id, :old_meta, :old_nbt, :old_amount, :new_id, :new_meta, :new_nbt, :new_amount); -- # } -- # } -- # } @@ -169,10 +170,6 @@ FROM status LIMIT 1; -- # } -- # {log --- # {last_id -SELECT MAX(log_id) AS lastId -FROM log_history; --- # } -- # {old_blocks -- # :log_ids list:int SELECT history_id, @@ -233,7 +230,8 @@ WHERE log_id IN :log_ids; -- # } -- # {entities -- # :log_ids list:int -SELECT e.entity_classpath, +SELECT log_id, + e.entity_classpath, el.entityfrom_id, el.entityfrom_nbt, x, diff --git a/resources/patches/.patches b/resources/patches/.patches index e8260e5f..7adb71d9 100644 --- a/resources/patches/.patches +++ b/resources/patches/.patches @@ -1,3 +1,11 @@ --- # !!! DON'T EDIT THIS FILE !!! +# --- YAML Patch format --- +# [VERSION]: +# sqlite: [NUMBER_OF_PATCHES] (For SQLite) +# mysql: [NUMBER_OF_PATCHES] (For MySQL) +# ------------------------- +0.6.0: + sqlite: 16 + mysql: 4 ... \ No newline at end of file diff --git a/resources/patches/mysql_patch.sql b/resources/patches/mysql_patch.sql index 5ffac0ec..88c31dfd 100644 --- a/resources/patches/mysql_patch.sql +++ b/resources/patches/mysql_patch.sql @@ -1,3 +1,17 @@ -- #!mysql -- #{patch +-- # {0.6.0 +-- # {1 +ALTER TABLE log_history ADD UNIQUE(log_id); +-- # } +-- # {2 +ALTER TABLE blocks_log ADD UNIQUE(history_id); +-- # } +-- # {3 +ALTER TABLE entities_log ADD UNIQUE(history_id); +-- # } +-- # {4 +ALTER TABLE inventories_log ADD UNIQUE(history_id); +-- # } +-- # } -- #} \ No newline at end of file diff --git a/resources/patches/sqlite_patch.sql b/resources/patches/sqlite_patch.sql index 9e6073ff..999c6cf0 100644 --- a/resources/patches/sqlite_patch.sql +++ b/resources/patches/sqlite_patch.sql @@ -1,3 +1,96 @@ -- #!sqlite -- #{patch +-- # {0.6.0 +-- # {1 +CREATE TABLE IF NOT EXISTS "temp" +( + log_id INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT NOT NULL, + who VARCHAR(36) NOT NULL, + x BIGINT NOT NULL, + y TINYINT UNSIGNED NOT NULL, + z BIGINT NOT NULL, + world_name VARCHAR(255) NOT NULL, + action TINYINT UNSIGNED NOT NULL, + time TIMESTAMP DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now', 'localtime')) NOT NULL, + "rollback" TINYINT(1) DEFAULT 0 NOT NULL, + FOREIGN KEY (who) REFERENCES "entities" (uuid) +); +-- # } +-- # {2 +INSERT INTO "temp" SELECT * FROM "log_history"; +-- # } +-- # {3 +DROP TABLE "log_history"; +-- # } +-- # {4 +ALTER TABLE "temp" RENAME TO "log_history"; +-- # } +-- # {5 +CREATE TABLE IF NOT EXISTS "temp" +( + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + old_id UNSIGNED INTEGER NOT NULL, + old_meta UNSIGNED TINYINT(2) NOT NULL, + old_nbt BLOB DEFAULT NULL, + new_id UNSIGNED INTEGER NOT NULL, + new_meta UNSIGNED TINYINT(2) NOT NULL, + new_nbt BLOB DEFAULT NULL, + FOREIGN KEY (history_id) REFERENCES "log_history" (log_id) ON DELETE CASCADE +); +-- # } +-- # {6 +INSERT INTO "temp" SELECT * FROM "blocks_log"; +-- # } +-- # {7 +DROP TABLE "blocks_log"; +-- # } +-- # {8 +ALTER TABLE "temp" RENAME TO "blocks_log"; +-- # } +-- # {9 +CREATE TABLE IF NOT EXISTS "temp" +( + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + entityfrom_uuid VARCHAR(36) NOT NULL, + entityfrom_id UNSIGNED INTEGER NOT NULL, + entityfrom_nbt BLOB DEFAULT NULL, + FOREIGN KEY (history_id) REFERENCES "log_history" (log_id) ON DELETE CASCADE, + FOREIGN KEY (entityfrom_uuid) REFERENCES "entities" (uuid) +); +-- # } +-- # {10 +INSERT INTO "temp" SELECT * FROM "entities_log"; +-- # } +-- # {11 +DROP TABLE "entities_log"; +-- # } +-- # {12 +ALTER TABLE "temp" RENAME TO "entities_log"; +-- # } +-- # {13 +CREATE TABLE IF NOT EXISTS "temp" +( + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + slot UNSIGNED TINYINT NOT NULL, + old_id UNSIGNED INTEGER DEFAULT 0 NOT NULL, + old_meta UNSIGNED TINYINT(2) DEFAULT 0 NOT NULL, + old_nbt BLOB DEFAULT NULL, + old_amount UNSIGNED TINYINT DEFAULT 0 NOT NULL, + new_id UNSIGNED INTEGER DEFAULT 0 NOT NULL, + new_meta UNSIGNED TINYINT(2) DEFAULT 0 NOT NULL, + new_nbt BLOB DEFAULT NULL, + new_amount UNSIGNED TINYINT DEFAULT 0 NOT NULL, + FOREIGN KEY (history_id) REFERENCES "log_history" (log_id) ON DELETE CASCADE +); +-- # } +-- # {14 +INSERT INTO "temp" SELECT * FROM "inventories_log"; +-- # } +-- # {15 +DROP TABLE "inventories_log"; +-- # } +-- # {16 +ALTER TABLE "temp" RENAME TO "inventories_log"; +-- # } +-- # } -- #} \ No newline at end of file diff --git a/resources/sqlite.sql b/resources/sqlite.sql index 333d15f7..60efa2b3 100644 --- a/resources/sqlite.sql +++ b/resources/sqlite.sql @@ -13,13 +13,13 @@ CREATE TABLE IF NOT EXISTS "entities" -- # {log_history CREATE TABLE IF NOT EXISTS "log_history" ( - log_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - who VARCHAR(36) NOT NULL, - x BIGINT NOT NULL, - y TINYINT UNSIGNED NOT NULL, - z BIGINT NOT NULL, - world_name VARCHAR(255) NOT NULL, - action TINYINT UNSIGNED NOT NULL, + log_id INTEGER UNIQUE PRIMARY KEY AUTOINCREMENT NOT NULL, + who VARCHAR(36) NOT NULL, + x BIGINT NOT NULL, + y TINYINT UNSIGNED NOT NULL, + z BIGINT NOT NULL, + world_name VARCHAR(255) NOT NULL, + action TINYINT UNSIGNED NOT NULL, time TIMESTAMP DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now', 'localtime')) NOT NULL, "rollback" TINYINT(1) DEFAULT 0 NOT NULL, FOREIGN KEY (who) REFERENCES "entities" (uuid) @@ -28,12 +28,12 @@ CREATE TABLE IF NOT EXISTS "log_history" -- # {blocks_log CREATE TABLE IF NOT EXISTS "blocks_log" ( - history_id UNSIGNED BIG INT NOT NULL, - old_id UNSIGNED INTEGER NOT NULL, - old_meta UNSIGNED TINYINT(2) NOT NULL, + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + old_id UNSIGNED INTEGER NOT NULL, + old_meta UNSIGNED TINYINT(2) NOT NULL, old_nbt BLOB DEFAULT NULL, - new_id UNSIGNED INTEGER NOT NULL, - new_meta UNSIGNED TINYINT(2) NOT NULL, + new_id UNSIGNED INTEGER NOT NULL, + new_meta UNSIGNED TINYINT(2) NOT NULL, new_nbt BLOB DEFAULT NULL, FOREIGN KEY (history_id) REFERENCES "log_history" (log_id) ON DELETE CASCADE ); @@ -41,9 +41,9 @@ CREATE TABLE IF NOT EXISTS "blocks_log" -- # {entities_log CREATE TABLE IF NOT EXISTS "entities_log" ( - history_id UNSIGNED BIG INT NOT NULL, - entityfrom_uuid VARCHAR(36) NOT NULL, - entityfrom_id UNSIGNED INTEGER NOT NULL, + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + entityfrom_uuid VARCHAR(36) NOT NULL, + entityfrom_id UNSIGNED INTEGER NOT NULL, entityfrom_nbt BLOB DEFAULT NULL, FOREIGN KEY (history_id) REFERENCES "log_history" (log_id) ON DELETE CASCADE, FOREIGN KEY (entityfrom_uuid) REFERENCES "entities" (uuid) @@ -52,8 +52,8 @@ CREATE TABLE IF NOT EXISTS "entities_log" -- # {inventories_log CREATE TABLE IF NOT EXISTS "inventories_log" ( - history_id UNSIGNED BIG INT NOT NULL, - slot UNSIGNED TINYINT NOT NULL, + history_id UNSIGNED BIG INT UNIQUE NOT NULL, + slot UNSIGNED TINYINT NOT NULL, old_id UNSIGNED INTEGER DEFAULT 0 NOT NULL, old_meta UNSIGNED TINYINT(2) DEFAULT 0 NOT NULL, old_nbt BLOB DEFAULT NULL, @@ -83,6 +83,11 @@ BEGIN TRANSACTION; END TRANSACTION; -- # } -- # } +-- # {pragma +-- # {foreign-keys-on +PRAGMA foreign_keys = ON; +-- # } +-- # } -- # {add -- # {entity -- # :uuid string @@ -113,6 +118,7 @@ INSERT INTO "log_history"(who, x, y, z, world_name, VALUES ((SELECT uuid FROM entities WHERE uuid = :uuid), :x, :y, :z, :world_name, :action); -- # } -- # {block +-- # :log_id int -- # :old_id int -- # :old_meta int -- # :old_nbt ?string @@ -121,17 +127,19 @@ VALUES ((SELECT uuid FROM entities WHERE uuid = :uuid), :x, :y, :z, :world_name, -- # :new_nbt ?string INSERT INTO "blocks_log"(history_id, old_id, old_meta, old_nbt, new_id, new_meta, new_nbt) -VALUES (LAST_INSERT_ROWID(), :old_id, :old_meta, :old_nbt, :new_id, :new_meta, +VALUES (:log_id, :old_id, :old_meta, :old_nbt, :new_id, :new_meta, :new_nbt); -- # } -- # {entity +-- # :log_id int -- # :uuid string -- # :id int -- # :nbt ?string INSERT INTO "entities_log"(history_id, entityfrom_uuid, entityfrom_id, entityfrom_nbt) -VALUES (LAST_INSERT_ROWID(), (SELECT uuid FROM entities WHERE uuid = :uuid), :id, :nbt); +VALUES (:log_id, (SELECT uuid FROM entities WHERE uuid = :uuid), :id, :nbt); -- # } -- # {inventory +-- # :log_id int -- # :slot int -- # :old_id int 0 -- # :old_meta int 0 @@ -143,7 +151,7 @@ VALUES (LAST_INSERT_ROWID(), (SELECT uuid FROM entities WHERE uuid = :uuid), :id -- # :new_amount int 0 INSERT INTO "inventories_log"(history_id, slot, old_id, old_meta, old_nbt, old_amount, new_id, new_meta, new_nbt, new_amount) -VALUES (LAST_INSERT_ROWID(), :slot, :old_id, :old_meta, :old_nbt, :old_amount, :new_id, +VALUES (:log_id, :slot, :old_id, :old_meta, :old_nbt, :old_amount, :new_id, :new_meta, :new_nbt, :new_amount); -- # } -- # } @@ -177,10 +185,6 @@ FROM status LIMIT 1; -- # } -- # {log --- # {last_id -SELECT MAX(log_id) AS lastId -FROM "log_history"; --- # } -- # {old_blocks -- # :log_ids list:int SELECT history_id, @@ -241,7 +245,8 @@ WHERE log_id IN :log_ids; -- # } -- # {entities -- # :log_ids list:int -SELECT e.entity_classpath, +SELECT log_id, + e.entity_classpath, el.entityfrom_id, el.entityfrom_nbt, x, diff --git a/src/matcracker/BedcoreProtect/Inspector.php b/src/matcracker/BedcoreProtect/Inspector.php index 4d86030a..23d634cc 100644 --- a/src/matcracker/BedcoreProtect/Inspector.php +++ b/src/matcracker/BedcoreProtect/Inspector.php @@ -22,19 +22,18 @@ namespace matcracker\BedcoreProtect; use matcracker\BedcoreProtect\enums\Action; +use matcracker\BedcoreProtect\utils\EntityUtils; use matcracker\BedcoreProtect\utils\Utils; use pocketmine\block\BlockFactory; use pocketmine\command\CommandSender; use pocketmine\item\ItemFactory; use pocketmine\Player; use pocketmine\utils\TextFormat; -use UnexpectedValueException; use function array_chunk; use function array_key_exists; use function count; use function is_int; use function strtotime; -use function var_export; final class Inspector { @@ -59,7 +58,7 @@ public static function addInspector(CommandSender $inspector): void private static function getSenderUUID(CommandSender $sender): string { - return ($sender instanceof Player ? $sender->getUniqueId() : $sender->getServer()->getServerUniqueId())->toString(); + return $sender instanceof Player ? EntityUtils::getUniqueId($sender) : $sender->getServer()->getServerUniqueId()->toString(); } /** @@ -92,7 +91,7 @@ public static function isInspector(CommandSender $inspector): bool return self::$inspectors[self::getSenderUUID($inspector)]['enabled'] ?? false; } - public static function cacheLogs(CommandSender $inspector, array $logs = []): void + public static function saveLogs(CommandSender $inspector, array $logs = []): void { self::$inspectors[self::getSenderUUID($inspector)]['logs'] = $logs; } @@ -102,12 +101,12 @@ public static function cacheLogs(CommandSender $inspector, array $logs = []): vo * * @return array */ - public static function getCachedLogs(CommandSender $inspector): array + public static function getSavedLogs(CommandSender $inspector): array { return self::$inspectors[self::getSenderUUID($inspector)]['logs'] ?? []; } - public static function clearCache(): void + public static function removeAll(): void { self::$inspectors = []; } @@ -165,7 +164,7 @@ public static function parseLogs(CommandSender $inspector, array $logs, int $pag if (isset($log["{$typeColumn}_amount"])) { $amount = (int)$log["{$typeColumn}_amount"]; - $itemName = ItemFactory::get($id, $meta)->getName(); + $itemName = ItemFactory::get($id, $meta)->getVanillaName(); $to = "{$amount} x #{$id}:{$meta} ({$itemName})"; } else { $blockName = BlockFactory::get($id, $meta)->getName(); @@ -174,7 +173,8 @@ public static function parseLogs(CommandSender $inspector, array $logs, int $pag } elseif (isset($log['entity_to'])) { $to = "#{$log['entity_to']}"; } else { - throw new UnexpectedValueException('Unexpected log parsed. Is your database up to date?' . var_export($log, true)); + $inspector->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c" . $lang->translateString('inspector.corrupted-data'))); + return; } //TODO: Use strikethrough (&m) when MC fix it. diff --git a/src/matcracker/BedcoreProtect/Main.php b/src/matcracker/BedcoreProtect/Main.php index 0c6c6889..f12dbd9a 100644 --- a/src/matcracker/BedcoreProtect/Main.php +++ b/src/matcracker/BedcoreProtect/Main.php @@ -23,9 +23,12 @@ use JackMD\UpdateNotifier\UpdateNotifier; use matcracker\BedcoreProtect\commands\BCPCommand; +use matcracker\BedcoreProtect\commands\CommandParser; +use matcracker\BedcoreProtect\listeners\BedcoreListener; use matcracker\BedcoreProtect\listeners\BlockListener; use matcracker\BedcoreProtect\listeners\BlockSniperListener; use matcracker\BedcoreProtect\listeners\EntityListener; +use matcracker\BedcoreProtect\listeners\InspectorListener; use matcracker\BedcoreProtect\listeners\PlayerListener; use matcracker\BedcoreProtect\listeners\WorldListener; use matcracker\BedcoreProtect\storage\Database; @@ -33,7 +36,6 @@ use matcracker\BedcoreProtect\utils\ConfigParser; use pocketmine\lang\BaseLang; use pocketmine\plugin\PluginBase; -use RuntimeException; use function mkdir; use function version_compare; @@ -43,18 +45,30 @@ final class Main extends PluginBase public const PLUGIN_TAG = "[" . self::PLUGIN_NAME . "]"; public const MESSAGE_PREFIX = "&3" . self::PLUGIN_NAME . " &f- "; - /** @var Main|null */ + /** @var Main */ private static $instance; + /** @var BaseLang */ + private $baseLang; /** @var Database */ private $database; /** @var ConfigParser */ private $configParser; /** @var ConfigParser */ private $oldConfigParser; - /** @var BaseLang */ - private $baseLang; /** @var bool */ private $bsHooked = false; + /** @var BedcoreListener[] */ + private $events; + + public static function getInstance(): Main + { + return self::$instance; + } + + public function getLanguage(): BaseLang + { + return $this->baseLang; + } public function getDatabase(): Database { @@ -85,6 +99,9 @@ public function reloadPlugin(): bool $this->configParser = (new ConfigParser($this->getConfig()))->validate(); if ($this->configParser->isValidConfig()) { + foreach ($this->events as $event) { + $event->config = $this->configParser; + } $this->baseLang = new BaseLang($this->configParser->getLanguage(), $this->getFile() . 'resources/languages/'); return true; } @@ -92,18 +109,12 @@ public function reloadPlugin(): bool return false; } - public static function getInstance(): Main - { - if (self::$instance === null) { - throw new RuntimeException("Invalid plugin instance detected."); - } - - return self::$instance; - } - public function onLoad(): void { self::$instance = $this; + + @mkdir($this->getDataFolder()); + $this->configParser = (new ConfigParser($this->getConfig()))->validate(); if (!$this->configParser->isValidConfig()) { $this->getServer()->getPluginManager()->disablePlugin($this); @@ -111,6 +122,8 @@ public function onLoad(): void return; } + $this->saveResource($this->configParser->getDatabaseFileName()); + $this->baseLang = new BaseLang($this->configParser->getLanguage(), $this->getFile() . 'resources/languages/'); if ($this->configParser->getBlockSniperHook()) { @@ -122,7 +135,7 @@ public function onLoad(): void } if ($this->configParser->getCheckUpdates()) { - UpdateNotifier::checkUpdate($this, $this->getName(), $this->getDescription()->getVersion()); + UpdateNotifier::checkUpdate($this->getName(), $this->getDescription()->getVersion()); } } @@ -130,22 +143,21 @@ public function onEnable(): void { $this->database = new Database($this); - @mkdir($this->getDataFolder()); - $this->saveResource('bedcore_database.db'); - + $pluginManager = $this->getServer()->getPluginManager(); //Database connection if (!$this->database->connect()) { - $this->getServer()->getPluginManager()->disablePlugin($this); - + $pluginManager->disablePlugin($this); return; } + + $queryManager = $this->database->getQueryManager(); + $version = $this->getVersion(); - $this->database->getQueries()->init($version); + $queryManager->init($version); $dbVersion = $this->database->getVersion(); if (version_compare($version, $dbVersion) < 0) { $this->getLogger()->warning($this->baseLang->translateString('database.version.higher')); - $this->getServer()->getPluginManager()->disablePlugin($this); - + $pluginManager->disablePlugin($this); return; } @@ -153,44 +165,33 @@ public function onEnable(): void $this->getLogger()->info($this->baseLang->translateString('database.version.updated', [$dbVersion, $version])); } + $queryManager->setupDefaultData(); + if ($this->configParser->isSQLite()) { - $this->database->getQueries()->beginTransaction(); - $this->getScheduler()->scheduleDelayedRepeatingTask(new SQLiteTransactionTask($this->database), SQLiteTransactionTask::getTicks(), SQLiteTransactionTask::getTicks()); + $pluginQueries = $queryManager->getPluginQueries(); + $pluginQueries->beginTransaction(); + $this->getScheduler()->scheduleDelayedRepeatingTask(new SQLiteTransactionTask($pluginQueries), SQLiteTransactionTask::getTicks(), SQLiteTransactionTask::getTicks()); } + CommandParser::initActions(); + $this->getServer()->getCommandMap()->register('bedcoreprotect', new BCPCommand($this)); //Registering events - $events = [ + $this->events = [ new BlockListener($this), new EntityListener($this), new PlayerListener($this), - new WorldListener($this) + new WorldListener($this), + new InspectorListener($this), + new BlockSniperListener($this) ]; - if ($this->bsHooked) { - $events[] = new BlockSniperListener($this); - } - - foreach ($events as $event) { - $this->getServer()->getPluginManager()->registerEvents($event, $this); + foreach ($this->events as $event) { + $pluginManager->registerEvents($event, $this); } } - public function isBlockSniperHooked(): bool - { - return $this->bsHooked; - } - - public function getLanguage(): BaseLang - { - if ($this->baseLang === null) { - throw new RuntimeException("Invalid language state detected."); - } - - return $this->baseLang; - } - /** * Returns the plugin version. * @return string @@ -200,14 +201,18 @@ public function getVersion(): string return $this->getDescription()->getVersion(); } + public function isBlockSniperHooked(): bool + { + return $this->bsHooked; + } + public function onDisable(): void { $this->getScheduler()->cancelAllTasks(); $this->database->disconnect(); - Inspector::clearCache(); - self::$instance = null; + Inspector::removeAll(); $this->bsHooked = false; - unset($this->database, $this->baseLang, $this->configParser, $this->oldConfigParser); + unset($this->database, $this->baseLang, $this->configParser, $this->oldConfigParser, $this->events); } } diff --git a/src/matcracker/BedcoreProtect/commands/BCPCommand.php b/src/matcracker/BedcoreProtect/commands/BCPCommand.php index f4b922df..46fd36db 100644 --- a/src/matcracker/BedcoreProtect/commands/BCPCommand.php +++ b/src/matcracker/BedcoreProtect/commands/BCPCommand.php @@ -22,12 +22,14 @@ namespace matcracker\BedcoreProtect\commands; use BlockHorizons\BlockSniper\sessions\SessionManager; +use Generator; use matcracker\BedcoreProtect\Inspector; use matcracker\BedcoreProtect\Main; use matcracker\BedcoreProtect\math\Area; use matcracker\BedcoreProtect\math\MathUtils; -use matcracker\BedcoreProtect\storage\queries\Queries; +use matcracker\BedcoreProtect\storage\QueryManager; use matcracker\BedcoreProtect\ui\Forms; +use matcracker\BedcoreProtect\utils\Utils; use pocketmine\command\Command; use pocketmine\command\CommandSender; use pocketmine\command\PluginIdentifiableCommand; @@ -36,19 +38,20 @@ use pocketmine\plugin\Plugin; use pocketmine\utils\TextFormat; use SOFe\AwaitGenerator\Await; -use function array_key_exists; use function count; use function ctype_digit; use function explode; use function implode; use function strtolower; +use function version_compare; +use const PHP_INT_MAX; final class BCPCommand extends Command implements PluginIdentifiableCommand { /** @var Main */ private $plugin; - /** @var Queries */ - private $queries; + /** @var QueryManager */ + private $queryManager; public function __construct(Main $plugin) { @@ -59,11 +62,13 @@ public function __construct(Main $plugin) ['core', 'co', 'bcp'] ); $this->plugin = $plugin; - $this->queries = $plugin->getDatabase()->getQueries(); + $this->queryManager = $plugin->getDatabase()->getQueryManager(); } public function execute(CommandSender $sender, string $commandLabel, array $args): bool { + $lang = $this->plugin->getLanguage(); + if (count($args) === 0) { $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c{$this->getUsage()}")); @@ -71,102 +76,112 @@ public function execute(CommandSender $sender, string $commandLabel, array $args } $subCmd = $this->removeAbbreviation(strtolower($args[0])); - if (!$sender->hasPermission('bcp.command.bedcoreprotect') || !$sender->hasPermission("bcp.subcommand.{$subCmd}")) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.no-permission'))); + if (!$sender->hasPermission("bcp.subcommand.{$subCmd}") || !$sender->hasPermission('bcp.command.bedcoreprotect')) { + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.no-permission'))); return false; } + $config = $this->plugin->getParsedConfig(); + //Shared commands between player and console. switch ($subCmd) { case "help": - array_key_exists(1, $args) ? BCPHelpCommand::showSpecificHelp($sender, $args[1]) : BCPHelpCommand::showGenericHelp($sender); + $helpCmd = new BCPHelpCommand($sender, $lang); + if (isset($args[1])) { + $helpCmd->showCommandHelp($args[1]); + } else { + $helpCmd->showGenericHelp(); + } return true; case 'reload': if ($this->plugin->reloadPlugin()) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.reload.success'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.reload.success'))); } else { $this->plugin->restoreParsedConfig(); - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.reload.no-success'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.reload.no-success'))); } return true; case 'status': - Await::f2c(function () use ($sender) { - $description = $this->plugin->getDescription(); - $lang = $this->plugin->getLanguage(); - $dbVersion = (string)(yield $this->plugin->getDatabase()->getStatus())[0]["version"]; - $sender->sendMessage(TextFormat::colorize('&f----- &3' . Main::PLUGIN_NAME . ' &f-----')); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.version', [$this->plugin->getVersion()]))); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.database-connection', [$this->plugin->getParsedConfig()->getPrintableDatabaseType()]))); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.database-version', [$dbVersion]))); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.blocksniper-hook', [$this->plugin->isBlockSniperHooked() ? $lang->translateString("generic.yes") : $lang->translateString("generic.no")]))); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.author', [implode(', ', $description->getAuthors())]))); - $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.website', [$description->getWebsite()]))); - }, function () { - //NOOP - }); + Await::f2c( + function () use ($sender, $config, $lang) : Generator { + $description = $this->plugin->getDescription(); + $pluginVersion = $description->getVersion(); + $dbVersion = (string)(yield $this->plugin->getDatabase()->getStatus())[0]["version"]; + + if (version_compare($pluginVersion, $dbVersion) > 0) { + //Database version could be minor respect the plugin, in this case I apply a BC suffix (Backward Compatibility) + $dbVersion .= ' (' . $lang->translateString("command.status.bc") . ')'; + } + $sender->sendMessage(TextFormat::colorize('&f----- &3' . Main::PLUGIN_NAME . ' &f-----')); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.version', [$pluginVersion]))); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.database-connection', [$config->getPrintableDatabaseType()]))); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.database-version', [$dbVersion]))); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.blocksniper-hook', [$this->plugin->isBlockSniperHooked() ? $lang->translateString("generic.yes") : $lang->translateString("generic.no")]))); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.author', [implode(', ', $description->getAuthors())]))); + $sender->sendMessage(TextFormat::colorize('&3' . $lang->translateString('command.status.website', [$description->getWebsite()]))); + } + ); return true; case 'lookup': - if (array_key_exists(1, $args)) { - $parser = new CommandParser($sender->getName(), $this->plugin->getParsedConfig(), $args, ['time'], true); + if (isset($args[1])) { + $parser = new CommandParser($sender->getName(), $config, $args, ['time'], true); if ($parser->parse()) { - $this->queries->requestLookup($sender, $parser); + $this->queryManager->getPluginQueries()->requestLookup($sender, $parser); } else { - if (count($logs = Inspector::getCachedLogs($sender)) > 0) { + if (count($logs = Inspector::getSavedLogs($sender)) > 0) { $page = 0; $lines = 4; - if (array_key_exists(1, $args)) { - $split = explode(":", $args[1]); - if ($ctype = ctype_digit($split[0])) { - $page = (int)$split[0]; - } + $split = explode(":", $args[1]); + if ($ctype = ctype_digit($split[0])) { + $page = (int)$split[0]; + } - if (array_key_exists(1, $split) && $ctype = ctype_digit($split[1])) { - $lines = (int)$split[1]; - } + if (isset($split[1]) && $ctype = ctype_digit($split[1])) { + $lines = (int)$split[1]; + } - if (!$ctype) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.no-numeric-value'))); + if (!$ctype) { + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.no-numeric-value'))); - return true; - } + return true; } + Inspector::parseLogs($sender, $logs, ($page - 1), $lines); } else { $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c{$parser->getErrorMessage()}")); } } } else { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.one-parameter'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.one-parameter'))); } return true; case 'purge': - if (array_key_exists(1, $args)) { - $parser = new CommandParser($sender->getName(), $this->plugin->getParsedConfig(), $args, ['time'], true); + if (isset($args[1])) { + $parser = new CommandParser($sender->getName(), $config, $args, ['time'], true); if ($parser->parse()) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.purge.started'))); - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.purge.no-restart'))); - $this->queries->purge($parser->getTime(), function (int $affectedRows) use ($sender): void { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.purge.success'))); - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.purge.deleted-rows', [$affectedRows]))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.purge.started'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.purge.no-restart'))); + $this->queryManager->getPluginQueries()->purge($parser->getTime() ?? PHP_INT_MAX, function (int $affectedRows) use ($sender, $lang): void { + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.purge.success'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.purge.deleted-rows', [$affectedRows]))); }); - return true; } else { $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c{$parser->getErrorMessage()}")); } } else { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.one-parameter'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.one-parameter'))); } return true; } if (!($sender instanceof Player)) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.no-console'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.no-console'))); return false; } @@ -175,67 +190,75 @@ public function execute(CommandSender $sender, string $commandLabel, array $args switch ($subCmd) { case 'menu': case 'ui': - $sender->sendForm((new Forms($this->plugin->getParsedConfig()))->getMainMenu()); + $sender->sendForm((new Forms($config))->getMainMenu()); return true; case 'inspect': if (Inspector::isInspector($sender)) { Inspector::removeInspector($sender); - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.inspect.disabled'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.inspect.disabled'))); } else { Inspector::addInspector($sender); - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.inspect.enabled'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.inspect.enabled'))); } return true; case 'near': $near = 5; - if (array_key_exists(1, $args)) { + if (isset($args[1])) { if (!ctype_digit($args[1])) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.no-numeric-value'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.no-numeric-value'))); return true; } $near = (int)$args[1]; - $maxRadius = $this->plugin->getParsedConfig()->getMaxRadius(); + $maxRadius = $config->getMaxRadius(); if ($near < 1 || $near > $maxRadius) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.near.range-value'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.near.range-value'))); return true; } } - $this->queries->requestNearLog($sender, $sender, $near); + $this->queryManager->getPluginQueries()->requestNearLog($sender, $sender, $near); return true; case 'rollback': - if (array_key_exists(1, $args)) { - $parser = new CommandParser($sender->getName(), $this->plugin->getParsedConfig(), $args, ['time', 'radius'], true); + if (isset($args[1])) { + $parser = new CommandParser($sender->getName(), $config, $args, ['time', 'radius'], true); if ($parser->parse()) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.rollback.started', [$sender->getLevel()->getName()]))); + $level = Utils::getLevelNonNull($sender->getLevel()); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.rollback.started', [$level->getName()]))); - $bb = $this->getSelectionArea($sender) ?? MathUtils::getRangedVector($sender->asVector3(), $parser->getRadius()); - $this->queries->rollback(new Area($sender->getLevel(), $bb), $parser); + $bb = $this->getSelectionArea($sender) ?? MathUtils::getRangedVector($sender->asVector3(), $parser->getRadius() ?? 0); + $this->queryManager->rollback(new Area($level, $bb), $parser); } else { $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c{$parser->getErrorMessage()}")); } } else { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.one-parameter'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.one-parameter'))); } return true; case 'restore': - if (array_key_exists(1, $args)) { - $parser = new CommandParser($sender->getName(), $this->plugin->getParsedConfig(), $args, ['time', 'radius'], true); + if (isset($args[1])) { + $parser = new CommandParser($sender->getName(), $config, $args, ['time', 'radius'], true); if ($parser->parse()) { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $this->plugin->getLanguage()->translateString('command.restore.started', [$sender->getLevel()->getName()]))); + $level = Utils::getLevelNonNull($sender->getLevel()); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.restore.started', [$level->getName()]))); - $bb = $this->getSelectionArea($sender) ?? MathUtils::getRangedVector($sender->asVector3(), $parser->getRadius()); - $this->queries->restore(new Area($sender->getLevel(), $bb), $parser); + $bb = $this->getSelectionArea($sender) ?? MathUtils::getRangedVector($sender->asVector3(), $parser->getRadius() ?? $config->getDefaultRadius()); + $this->queryManager->restore(new Area($level, $bb), $parser); } else { $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . "&c{$parser->getErrorMessage()}")); } } else { - $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $this->plugin->getLanguage()->translateString('command.error.one-parameter'))); + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . '&c' . $lang->translateString('command.error.one-parameter'))); + } + + return true; + case 'undo': + if (!$this->queryManager->undoRollback($sender)) { + $sender->sendMessage(TextFormat::colorize(Main::MESSAGE_PREFIX . $lang->translateString('command.undo.not-found'))); } return true; @@ -277,6 +300,9 @@ private function getSelectionArea(Player $player): ?AxisAlignedBB return null; } + /** + * @return Main + */ public function getPlugin(): Plugin { return $this->plugin; diff --git a/src/matcracker/BedcoreProtect/commands/BCPHelpCommand.php b/src/matcracker/BedcoreProtect/commands/BCPHelpCommand.php index 942a1b4d..b5d77e59 100644 --- a/src/matcracker/BedcoreProtect/commands/BCPHelpCommand.php +++ b/src/matcracker/BedcoreProtect/commands/BCPHelpCommand.php @@ -23,53 +23,59 @@ use matcracker\BedcoreProtect\Main; use pocketmine\command\CommandSender; +use pocketmine\lang\BaseLang; use pocketmine\utils\TextFormat; use function strtolower; final class BCPHelpCommand { - private function __construct() + /** @var CommandSender */ + private $sender; + /** @var BaseLang */ + private $lang; + + public function __construct(CommandSender $sender, BaseLang $lang) { + $this->sender = $sender; + $this->lang = $lang; } - public static function showGenericHelp(CommandSender $sender): void + public function showGenericHelp(): void { - $lang = Main::getInstance()->getLanguage(); - $sender->sendMessage(TextFormat::colorize("&f----- &3" . Main::PLUGIN_NAME . " &3" . $lang->translateString("command.help.title") . " &f-----")); - $sender->sendMessage(TextFormat::colorize("&3/bcp help &7 &f- " . $lang->translateString("command.help.help"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7menu &f- " . $lang->translateString("command.help.menu"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7inspect &f- " . $lang->translateString("command.help.inspect"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7rollback &3 &f- " . $lang->translateString("command.help.rollback"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7restore &3 &f- " . $lang->translateString("command.help.restore"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7lookup &3 &f- " . $lang->translateString("command.help.lookup"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7purge &3 &f- " . $lang->translateString("command.help.purge"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7reload &f- " . $lang->translateString("command.help.reload"))); - $sender->sendMessage(TextFormat::colorize("&3/bcp &7status &f- " . $lang->translateString("command.help.status"))); - $sender->sendMessage(TextFormat::colorize("&f------")); + $this->sender->sendMessage(TextFormat::colorize("&f----- &3" . Main::PLUGIN_NAME . " &3" . $this->lang->translateString("command.help.title") . " &f-----")); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp help &7 &f- " . $this->lang->translateString("command.help.help"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7menu &f- " . $this->lang->translateString("command.help.menu"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7inspect &f- " . $this->lang->translateString("command.help.inspect"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7rollback &3 &f- " . $this->lang->translateString("command.help.rollback"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7restore &3 &f- " . $this->lang->translateString("command.help.restore"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7lookup &3 &f- " . $this->lang->translateString("command.help.lookup"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7purge &3 &f- " . $this->lang->translateString("command.help.purge"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7reload &f- " . $this->lang->translateString("command.help.reload"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp &7status &f- " . $this->lang->translateString("command.help.status"))); + $this->sender->sendMessage(TextFormat::colorize("&f------")); } - public static function showSpecificHelp(CommandSender $sender, string $subCmd): void + public function showCommandHelp(string $subCmd): void { - $lang = Main::getInstance()->getLanguage(); $subCmd = strtolower($subCmd); - $sender->sendMessage(TextFormat::colorize("&f----- &3" . Main::PLUGIN_NAME . " &3" . $lang->translateString("command.help.title") . "&f-----")); + $this->sender->sendMessage(TextFormat::colorize("&f----- &3" . Main::PLUGIN_NAME . " &3" . $this->lang->translateString("command.help.title") . "&f-----")); switch ($subCmd) { case "help": - $sender->sendMessage(TextFormat::colorize("&3/bcp help &f- " . $lang->translateString("command.help.help2"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp help &f- " . $this->lang->translateString("command.help.help2"))); break; case "menu": case "ui": - $sender->sendMessage(TextFormat::colorize("&3/bcp menu &f- " . $lang->translateString("command.help.menu"))); + $this->sender->sendMessage(TextFormat::colorize("&3/bcp menu &f- " . $this->lang->translateString("command.help.menu"))); break; case "inspect": case "i": - $sender->sendMessage(TextFormat::colorize("&3" . $lang->translateString("command.help.inspect1"))); - $sender->sendMessage(TextFormat::colorize("&7* " . $lang->translateString("command.help.inspect2"))); - $sender->sendMessage(TextFormat::colorize("&7* " . $lang->translateString("command.help.inspect3"))); - $sender->sendMessage(TextFormat::colorize("&7* " . $lang->translateString("command.help.inspect4"))); - $sender->sendMessage(TextFormat::colorize("&7* " . $lang->translateString("command.help.inspect5"))); - $sender->sendMessage(TextFormat::colorize("&7* " . $lang->translateString("command.help.inspect6"))); - $sender->sendMessage(TextFormat::colorize("&7" . $lang->translateString("command.help.inspect7"))); + $this->sender->sendMessage(TextFormat::colorize("&3" . $this->lang->translateString("command.help.inspect1"))); + $this->sender->sendMessage(TextFormat::colorize("&7* " . $this->lang->translateString("command.help.inspect2"))); + $this->sender->sendMessage(TextFormat::colorize("&7* " . $this->lang->translateString("command.help.inspect3"))); + $this->sender->sendMessage(TextFormat::colorize("&7* " . $this->lang->translateString("command.help.inspect4"))); + $this->sender->sendMessage(TextFormat::colorize("&7* " . $this->lang->translateString("command.help.inspect5"))); + $this->sender->sendMessage(TextFormat::colorize("&7* " . $this->lang->translateString("command.help.inspect6"))); + $this->sender->sendMessage(TextFormat::colorize("&7" . $this->lang->translateString("command.help.inspect7"))); break; case "rollback": case "rb": @@ -83,61 +89,61 @@ public static function showSpecificHelp(CommandSender $sender, string $subCmd): } elseif ($subCmd === "rb") { $subCmd = "rollback"; } - $sender->sendMessage(TextFormat::colorize("&3/bcp {$subCmd} &7 &f- " . $lang->translateString("command.help.parameters1", [$subCmd]))); - $sender->sendMessage(TextFormat::colorize("&3| &7u= &f- " . $lang->translateString("command.help.parameters2", [$subCmd]))); - $sender->sendMessage(TextFormat::colorize("&3| &7t=