001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.command; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.backup.BackupManager; 023import com.plotsquared.core.backup.BackupProfile; 024import com.plotsquared.core.backup.NullBackupProfile; 025import com.plotsquared.core.backup.PlayerBackupProfile; 026import com.plotsquared.core.configuration.caption.TranslatableCaption; 027import com.plotsquared.core.exception.PlotSquaredException; 028import com.plotsquared.core.permissions.Permission; 029import com.plotsquared.core.player.PlotPlayer; 030import com.plotsquared.core.plot.Plot; 031import com.plotsquared.core.util.task.RunnableVal2; 032import com.plotsquared.core.util.task.RunnableVal3; 033import net.kyori.adventure.text.Component; 034import net.kyori.adventure.text.minimessage.tag.Tag; 035import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 036import org.apache.logging.log4j.LogManager; 037import org.apache.logging.log4j.Logger; 038import org.checkerframework.checker.nullness.qual.NonNull; 039 040import java.nio.file.Files; 041import java.time.Instant; 042import java.time.ZoneId; 043import java.time.ZonedDateTime; 044import java.time.format.DateTimeFormatter; 045import java.util.ArrayList; 046import java.util.Arrays; 047import java.util.Collection; 048import java.util.List; 049import java.util.Locale; 050import java.util.Objects; 051import java.util.concurrent.CompletableFuture; 052import java.util.stream.Collectors; 053import java.util.stream.IntStream; 054import java.util.stream.Stream; 055 056@CommandDeclaration(command = "backup", 057 usage = "/plot backup <save | list | load>", 058 category = CommandCategory.SETTINGS, 059 requiredType = RequiredType.PLAYER, 060 permission = "plots.backup") 061public final class Backup extends Command { 062 063 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Backup.class.getSimpleName()); 064 065 private final BackupManager backupManager; 066 067 @Inject 068 public Backup(final @NonNull BackupManager backupManager) { 069 super(MainCommand.getInstance(), true); 070 this.backupManager = backupManager; 071 } 072 073 private static boolean sendMessage(PlotPlayer<?> player) { 074 player.sendMessage( 075 TranslatableCaption.of("commandconfig.command_syntax"), 076 TagResolver.resolver("value", Tag.inserting(Component.text("/plot backup <save | list | load>"))) 077 ); 078 return true; 079 } 080 081 @Override 082 public CompletableFuture<Boolean> execute( 083 PlotPlayer<?> player, String[] args, 084 RunnableVal3<Command, Runnable, Runnable> confirm, 085 RunnableVal2<Command, CommandResult> whenDone 086 ) throws CommandException { 087 if (args.length == 0 || !Arrays.asList("save", "list", "load") 088 .contains(args[0].toLowerCase(Locale.ENGLISH))) { 089 return CompletableFuture.completedFuture(sendMessage(player)); 090 } 091 return super.execute(player, args, confirm, whenDone); 092 } 093 094 @Override 095 public Collection<Command> tab(PlotPlayer<?> player, String[] args, boolean space) { 096 if (args.length == 1) { 097 return Stream.of("save", "list", "load") 098 .filter(value -> value.startsWith(args[0].toLowerCase(Locale.ENGLISH))) 099 .map(value -> new Command(null, false, value, "", RequiredType.NONE, null) { 100 }).collect(Collectors.toList()); 101 } else if (args[0].equalsIgnoreCase("load")) { 102 103 final Plot plot = player.getCurrentPlot(); 104 if (plot != null) { 105 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 106 if (backupProfile instanceof PlayerBackupProfile) { 107 final CompletableFuture<List<com.plotsquared.core.backup.Backup>> backupList = 108 backupProfile.listBackups(); 109 if (backupList.isDone()) { 110 final List<com.plotsquared.core.backup.Backup> backups = 111 backupList.getNow(new ArrayList<>()); 112 if (backups.isEmpty()) { 113 return new ArrayList<>(); 114 } 115 return IntStream.range(1, 1 + backups.size()).mapToObj( 116 i -> new Command(null, false, Integer.toString(i), "", 117 RequiredType.NONE, null 118 ) { 119 }).collect(Collectors.toList()); 120 121 } 122 } 123 } 124 } 125 return tabOf(player, args, space); 126 } 127 128 @CommandDeclaration(command = "save", 129 usage = "/plot backup save", 130 category = CommandCategory.SETTINGS, 131 requiredType = RequiredType.PLAYER, 132 permission = "plots.backup.save") 133 public void save( 134 final Command command, final PlotPlayer<?> player, final String[] args, 135 final RunnableVal3<Command, Runnable, Runnable> confirm, 136 final RunnableVal2<Command, CommandResult> whenDone 137 ) { 138 final Plot plot = player.getCurrentPlot(); 139 if (plot == null) { 140 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 141 } else if (!plot.hasOwner()) { 142 player.sendMessage( 143 TranslatableCaption.of("backups.backup_impossible"), 144 TagResolver.resolver("plot", Tag.inserting( 145 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 146 )) 147 ); 148 } else if (plot.getVolume() > Integer.MAX_VALUE) { 149 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 150 } else if (plot.isMerged()) { 151 player.sendMessage( 152 TranslatableCaption.of("backups.backup_impossible"), 153 TagResolver.resolver("plot", Tag.inserting( 154 TranslatableCaption.of("generic.generic_merged").toComponent(player) 155 )) 156 ); 157 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 158 player.sendMessage( 159 TranslatableCaption.of("permission.no_permission"), 160 TagResolver.resolver( 161 "node", 162 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 163 ) 164 ); 165 } else { 166 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 167 if (backupProfile instanceof NullBackupProfile) { 168 player.sendMessage( 169 TranslatableCaption.of("backups.backup_impossible"), 170 TagResolver.resolver( 171 "plot", Tag.inserting(TranslatableCaption 172 .of("generic.generic_other") 173 .toComponent(player)) 174 ) 175 ); 176 } else { 177 backupProfile.createBackup().whenComplete((backup, throwable) -> { 178 if (throwable != null) { 179 player.sendMessage( 180 TranslatableCaption.of("backups.backup_save_failed"), 181 TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) 182 ); 183 throwable.printStackTrace(); 184 } else { 185 player.sendMessage(TranslatableCaption.of("backups.backup_save_success")); 186 } 187 }); 188 } 189 } 190 } 191 192 @CommandDeclaration(command = "list", 193 usage = "/plot backup list", 194 category = CommandCategory.SETTINGS, 195 requiredType = RequiredType.PLAYER, 196 permission = "plots.backup.list") 197 public void list( 198 final Command command, final PlotPlayer<?> player, final String[] args, 199 final RunnableVal3<Command, Runnable, Runnable> confirm, 200 final RunnableVal2<Command, CommandResult> whenDone 201 ) { 202 final Plot plot = player.getCurrentPlot(); 203 if (plot == null) { 204 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 205 } else if (!plot.hasOwner()) { 206 player.sendMessage( 207 TranslatableCaption.of("backups.backup_impossible"), 208 TagResolver.resolver("plot", Tag.inserting( 209 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 210 )) 211 ); 212 } else if (plot.isMerged()) { 213 player.sendMessage( 214 TranslatableCaption.of("backups.backup_impossible"), 215 TagResolver.resolver("plot", Tag.inserting( 216 TranslatableCaption.of("generic.generic_merged").toComponent(player) 217 )) 218 ); 219 } else if (plot.getVolume() > Integer.MAX_VALUE) { 220 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 221 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 222 player.sendMessage( 223 TranslatableCaption.of("permission.no_permission"), 224 TagResolver.resolver( 225 "node", 226 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 227 ) 228 ); 229 } else { 230 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 231 if (backupProfile instanceof NullBackupProfile) { 232 player.sendMessage( 233 TranslatableCaption.of("backups.backup_impossible"), 234 TagResolver.resolver("plot", Tag.inserting( 235 TranslatableCaption.of("generic.generic_other").toComponent(player) 236 )) 237 ); 238 } else { 239 backupProfile.listBackups().whenComplete((backups, throwable) -> { 240 if (throwable != null) { 241 player.sendMessage( 242 TranslatableCaption.of("backups.backup_list_failed"), 243 TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) 244 ); 245 throwable.printStackTrace(); 246 } else { 247 player.sendMessage( 248 TranslatableCaption.of("backups.backup_list_header"), 249 TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toCommaSeparatedString()))) 250 ); 251 try { 252 for (int i = 0; i < backups.size(); i++) { 253 player.sendMessage( 254 TranslatableCaption.of("backups.backup_list_entry"), 255 TagResolver.builder() 256 .tag("number", Tag.inserting(Component.text(i + 1))) 257 .tag( 258 "value", 259 Tag.inserting(Component.text(DateTimeFormatter.RFC_1123_DATE_TIME.format( 260 ZonedDateTime.ofInstant( 261 Instant.ofEpochMilli(backups.get(i).getCreationTime()), 262 ZoneId.systemDefault() 263 )))) 264 ) 265 .build() 266 ); 267 } 268 } catch (final Exception e) { 269 e.printStackTrace(); 270 } 271 } 272 }); 273 } 274 } 275 } 276 277 @CommandDeclaration(command = "load", 278 usage = "/plot backup load <#>", 279 category = CommandCategory.SETTINGS, 280 requiredType = RequiredType.PLAYER, 281 permission = "plots.backup.load") 282 public void load( 283 final Command command, final PlotPlayer<?> player, final String[] args, 284 final RunnableVal3<Command, Runnable, Runnable> confirm, 285 final RunnableVal2<Command, CommandResult> whenDone 286 ) { 287 final Plot plot = player.getCurrentPlot(); 288 if (plot == null) { 289 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 290 } else if (!plot.hasOwner()) { 291 player.sendMessage( 292 TranslatableCaption.of("backups.backup_impossible"), 293 TagResolver.resolver("plot", Tag.inserting( 294 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 295 )) 296 ); 297 } else if (plot.isMerged()) { 298 player.sendMessage( 299 TranslatableCaption.of("backups.backup_impossible"), 300 TagResolver.resolver("plot", Tag.inserting( 301 TranslatableCaption.of("generic.generic_merged").toComponent(player) 302 )) 303 ); 304 } else if (plot.getVolume() > Integer.MAX_VALUE) { 305 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 306 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 307 player.sendMessage( 308 TranslatableCaption.of("permission.no_permission"), 309 TagResolver.resolver( 310 "node", 311 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 312 ) 313 ); 314 } else if (args.length == 0) { 315 player.sendMessage( 316 TranslatableCaption.of("commandconfig.command_syntax"), 317 TagResolver.resolver("value", Tag.inserting(Component.text("Usage: /plot backup save/list/load"))) 318 ); 319 } else { 320 final int number; 321 try { 322 number = Integer.parseInt(args[0]); 323 } catch (final Exception e) { 324 player.sendMessage( 325 TranslatableCaption.of("invalid.not_a_number"), 326 TagResolver.resolver("value", Tag.inserting(Component.text(args[0]))) 327 ); 328 return; 329 } 330 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 331 if (backupProfile instanceof NullBackupProfile) { 332 player.sendMessage( 333 TranslatableCaption.of("backups.backup_impossible"), 334 TagResolver.resolver( 335 "plot", Tag.inserting( 336 TranslatableCaption.of("generic.generic_other").toComponent(player) 337 ) 338 ) 339 ); 340 } else { 341 backupProfile.listBackups().whenComplete((backups, throwable) -> { 342 if (throwable != null) { 343 Component reason; 344 if (throwable instanceof PlotSquaredException pe) { 345 reason = pe.getCaption().toComponent(player); 346 } else { 347 reason = Component.text(throwable.getMessage()); 348 } 349 player.sendMessage( 350 TranslatableCaption.of("backups.backup_load_failure"), 351 TagResolver.resolver("reason", Tag.inserting(reason)) 352 ); 353 LOGGER.error("Error loading player ({}) backup", player.getName(), throwable); 354 return; 355 } 356 if (number < 1 || number > backups.size()) { 357 player.sendMessage( 358 TranslatableCaption.of("backups.backup_impossible"), 359 TagResolver.resolver( 360 "plot", 361 Tag.inserting(TranslatableCaption 362 .of("generic.generic_invalid_choice") 363 .toComponent(player)) 364 ) 365 ); 366 } else { 367 final com.plotsquared.core.backup.Backup backup = 368 backups.get(number - 1); 369 if (backup == null || backup.getFile() == null || !Files 370 .exists(backup.getFile())) { 371 player.sendMessage( 372 TranslatableCaption.of("backups.backup_impossible"), 373 TagResolver.resolver( 374 "plot", 375 Tag.inserting(TranslatableCaption 376 .of("generic.generic_invalid_choice") 377 .toComponent(player)) 378 ) 379 ); 380 } else { 381 CmdConfirm.addPending( 382 player, "/plot backup load " + number, 383 () -> backupProfile.restoreBackup(backup, player) 384 .whenComplete((n, error) -> { 385 if (error != null) { 386 player.sendMessage( 387 TranslatableCaption.of("backups.backup_load_failure"), 388 TagResolver.resolver( 389 "reason", 390 Tag.inserting(Component.text(error.getMessage())) 391 ) 392 ); 393 } else { 394 player.sendMessage(TranslatableCaption.of("backups.backup_load_success")); 395 } 396 }) 397 ); 398 } 399 } 400 }); 401 } 402 } 403 } 404 405}