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.permissions.Permission; 028import com.plotsquared.core.player.PlotPlayer; 029import com.plotsquared.core.plot.Plot; 030import com.plotsquared.core.util.task.RunnableVal2; 031import com.plotsquared.core.util.task.RunnableVal3; 032import net.kyori.adventure.text.Component; 033import net.kyori.adventure.text.minimessage.tag.Tag; 034import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 035import org.checkerframework.checker.nullness.qual.NonNull; 036 037import java.nio.file.Files; 038import java.time.Instant; 039import java.time.ZoneId; 040import java.time.ZonedDateTime; 041import java.time.format.DateTimeFormatter; 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.Collection; 045import java.util.List; 046import java.util.Locale; 047import java.util.Objects; 048import java.util.concurrent.CompletableFuture; 049import java.util.stream.Collectors; 050import java.util.stream.IntStream; 051import java.util.stream.Stream; 052 053@CommandDeclaration(command = "backup", 054 usage = "/plot backup <save | list | load>", 055 category = CommandCategory.SETTINGS, 056 requiredType = RequiredType.PLAYER, 057 permission = "plots.backup") 058public final class Backup extends Command { 059 060 private final BackupManager backupManager; 061 062 @Inject 063 public Backup(final @NonNull BackupManager backupManager) { 064 super(MainCommand.getInstance(), true); 065 this.backupManager = backupManager; 066 } 067 068 private static boolean sendMessage(PlotPlayer<?> player) { 069 player.sendMessage( 070 TranslatableCaption.of("commandconfig.command_syntax"), 071 TagResolver.resolver("value", Tag.inserting(Component.text("/plot backup <save | list | load>"))) 072 ); 073 return true; 074 } 075 076 @Override 077 public CompletableFuture<Boolean> execute( 078 PlotPlayer<?> player, String[] args, 079 RunnableVal3<Command, Runnable, Runnable> confirm, 080 RunnableVal2<Command, CommandResult> whenDone 081 ) throws CommandException { 082 if (args.length == 0 || !Arrays.asList("save", "list", "load") 083 .contains(args[0].toLowerCase(Locale.ENGLISH))) { 084 return CompletableFuture.completedFuture(sendMessage(player)); 085 } 086 return super.execute(player, args, confirm, whenDone); 087 } 088 089 @Override 090 public Collection<Command> tab(PlotPlayer<?> player, String[] args, boolean space) { 091 if (args.length == 1) { 092 return Stream.of("save", "list", "load") 093 .filter(value -> value.startsWith(args[0].toLowerCase(Locale.ENGLISH))) 094 .map(value -> new Command(null, false, value, "", RequiredType.NONE, null) { 095 }).collect(Collectors.toList()); 096 } else if (args[0].equalsIgnoreCase("load")) { 097 098 final Plot plot = player.getCurrentPlot(); 099 if (plot != null) { 100 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 101 if (backupProfile instanceof PlayerBackupProfile) { 102 final CompletableFuture<List<com.plotsquared.core.backup.Backup>> backupList = 103 backupProfile.listBackups(); 104 if (backupList.isDone()) { 105 final List<com.plotsquared.core.backup.Backup> backups = 106 backupList.getNow(new ArrayList<>()); 107 if (backups.isEmpty()) { 108 return new ArrayList<>(); 109 } 110 return IntStream.range(1, 1 + backups.size()).mapToObj( 111 i -> new Command(null, false, Integer.toString(i), "", 112 RequiredType.NONE, null 113 ) { 114 }).collect(Collectors.toList()); 115 116 } 117 } 118 } 119 } 120 return tabOf(player, args, space); 121 } 122 123 @CommandDeclaration(command = "save", 124 usage = "/plot backup save", 125 category = CommandCategory.SETTINGS, 126 requiredType = RequiredType.PLAYER, 127 permission = "plots.backup.save") 128 public void save( 129 final Command command, final PlotPlayer<?> player, final String[] args, 130 final RunnableVal3<Command, Runnable, Runnable> confirm, 131 final RunnableVal2<Command, CommandResult> whenDone 132 ) { 133 final Plot plot = player.getCurrentPlot(); 134 if (plot == null) { 135 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 136 } else if (!plot.hasOwner()) { 137 player.sendMessage( 138 TranslatableCaption.of("backups.backup_impossible"), 139 TagResolver.resolver("plot", Tag.inserting( 140 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 141 )) 142 ); 143 } else if (plot.getVolume() > Integer.MAX_VALUE) { 144 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 145 } else if (plot.isMerged()) { 146 player.sendMessage( 147 TranslatableCaption.of("backups.backup_impossible"), 148 TagResolver.resolver("plot", Tag.inserting( 149 TranslatableCaption.of("generic.generic_merged").toComponent(player) 150 )) 151 ); 152 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 153 player.sendMessage( 154 TranslatableCaption.of("permission.no_permission"), 155 TagResolver.resolver( 156 "node", 157 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 158 ) 159 ); 160 } else { 161 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 162 if (backupProfile instanceof NullBackupProfile) { 163 player.sendMessage( 164 TranslatableCaption.of("backups.backup_impossible"), 165 TagResolver.resolver( 166 "plot", Tag.inserting(TranslatableCaption 167 .of("generic.generic_other") 168 .toComponent(player)) 169 ) 170 ); 171 } else { 172 backupProfile.createBackup().whenComplete((backup, throwable) -> { 173 if (throwable != null) { 174 player.sendMessage( 175 TranslatableCaption.of("backups.backup_save_failed"), 176 TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) 177 ); 178 throwable.printStackTrace(); 179 } else { 180 player.sendMessage(TranslatableCaption.of("backups.backup_save_success")); 181 } 182 }); 183 } 184 } 185 } 186 187 @CommandDeclaration(command = "list", 188 usage = "/plot backup list", 189 category = CommandCategory.SETTINGS, 190 requiredType = RequiredType.PLAYER, 191 permission = "plots.backup.list") 192 public void list( 193 final Command command, final PlotPlayer<?> player, final String[] args, 194 final RunnableVal3<Command, Runnable, Runnable> confirm, 195 final RunnableVal2<Command, CommandResult> whenDone 196 ) { 197 final Plot plot = player.getCurrentPlot(); 198 if (plot == null) { 199 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 200 } else if (!plot.hasOwner()) { 201 player.sendMessage( 202 TranslatableCaption.of("backups.backup_impossible"), 203 TagResolver.resolver("plot", Tag.inserting( 204 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 205 )) 206 ); 207 } else if (plot.isMerged()) { 208 player.sendMessage( 209 TranslatableCaption.of("backups.backup_impossible"), 210 TagResolver.resolver("plot", Tag.inserting( 211 TranslatableCaption.of("generic.generic_merged").toComponent(player) 212 )) 213 ); 214 } else if (plot.getVolume() > Integer.MAX_VALUE) { 215 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 216 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 217 player.sendMessage( 218 TranslatableCaption.of("permission.no_permission"), 219 TagResolver.resolver( 220 "node", 221 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 222 ) 223 ); 224 } else { 225 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 226 if (backupProfile instanceof NullBackupProfile) { 227 player.sendMessage( 228 TranslatableCaption.of("backups.backup_impossible"), 229 TagResolver.resolver("plot", Tag.inserting( 230 TranslatableCaption.of("generic.generic_other").toComponent(player) 231 )) 232 ); 233 } else { 234 backupProfile.listBackups().whenComplete((backups, throwable) -> { 235 if (throwable != null) { 236 player.sendMessage( 237 TranslatableCaption.of("backups.backup_list_failed"), 238 TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) 239 ); 240 throwable.printStackTrace(); 241 } else { 242 player.sendMessage( 243 TranslatableCaption.of("backups.backup_list_header"), 244 TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toCommaSeparatedString()))) 245 ); 246 try { 247 for (int i = 0; i < backups.size(); i++) { 248 player.sendMessage( 249 TranslatableCaption.of("backups.backup_list_entry"), 250 TagResolver.builder() 251 .tag("number", Tag.inserting(Component.text(i + 1))) 252 .tag( 253 "value", 254 Tag.inserting(Component.text(DateTimeFormatter.RFC_1123_DATE_TIME.format( 255 ZonedDateTime.ofInstant( 256 Instant.ofEpochMilli(backups.get(i).getCreationTime()), 257 ZoneId.systemDefault() 258 )))) 259 ) 260 .build() 261 ); 262 } 263 } catch (final Exception e) { 264 e.printStackTrace(); 265 } 266 } 267 }); 268 } 269 } 270 } 271 272 @CommandDeclaration(command = "load", 273 usage = "/plot backup load <#>", 274 category = CommandCategory.SETTINGS, 275 requiredType = RequiredType.PLAYER, 276 permission = "plots.backup.load") 277 public void load( 278 final Command command, final PlotPlayer<?> player, final String[] args, 279 final RunnableVal3<Command, Runnable, Runnable> confirm, 280 final RunnableVal2<Command, CommandResult> whenDone 281 ) { 282 final Plot plot = player.getCurrentPlot(); 283 if (plot == null) { 284 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 285 } else if (!plot.hasOwner()) { 286 player.sendMessage( 287 TranslatableCaption.of("backups.backup_impossible"), 288 TagResolver.resolver("plot", Tag.inserting( 289 TranslatableCaption.of("generic.generic_unowned").toComponent(player) 290 )) 291 ); 292 } else if (plot.isMerged()) { 293 player.sendMessage( 294 TranslatableCaption.of("backups.backup_impossible"), 295 TagResolver.resolver("plot", Tag.inserting( 296 TranslatableCaption.of("generic.generic_merged").toComponent(player) 297 )) 298 ); 299 } else if (plot.getVolume() > Integer.MAX_VALUE) { 300 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 301 } else if (!plot.isOwner(player.getUUID()) && !player.hasPermission(Permission.PERMISSION_ADMIN_BACKUP_OTHER)) { 302 player.sendMessage( 303 TranslatableCaption.of("permission.no_permission"), 304 TagResolver.resolver( 305 "node", 306 Tag.inserting(Permission.PERMISSION_ADMIN_BACKUP_OTHER) 307 ) 308 ); 309 } else if (args.length == 0) { 310 player.sendMessage( 311 TranslatableCaption.of("commandconfig.command_syntax"), 312 TagResolver.resolver("value", Tag.inserting(Component.text("Usage: /plot backup save/list/load"))) 313 ); 314 } else { 315 final int number; 316 try { 317 number = Integer.parseInt(args[0]); 318 } catch (final Exception e) { 319 player.sendMessage( 320 TranslatableCaption.of("invalid.not_a_number"), 321 TagResolver.resolver("value", Tag.inserting(Component.text(args[0]))) 322 ); 323 return; 324 } 325 final BackupProfile backupProfile = Objects.requireNonNull(this.backupManager.getProfile(plot)); 326 if (backupProfile instanceof NullBackupProfile) { 327 player.sendMessage( 328 TranslatableCaption.of("backups.backup_impossible"), 329 TagResolver.resolver("plot", Tag.inserting( 330 TranslatableCaption.of("generic.generic_other").toComponent(player) 331 )) 332 ); 333 } else { 334 backupProfile.listBackups().whenComplete((backups, throwable) -> { 335 if (throwable != null) { 336 player.sendMessage( 337 TranslatableCaption.of("backups.backup_load_failure"), 338 TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) 339 ); 340 throwable.printStackTrace(); 341 } else { 342 if (number < 1 || number > backups.size()) { 343 player.sendMessage( 344 TranslatableCaption.of("backups.backup_impossible"), 345 TagResolver.resolver( 346 "plot", 347 Tag.inserting(TranslatableCaption 348 .of("generic.generic_invalid_choice") 349 .toComponent(player)) 350 ) 351 ); 352 } else { 353 final com.plotsquared.core.backup.Backup backup = 354 backups.get(number - 1); 355 if (backup == null || backup.getFile() == null || !Files 356 .exists(backup.getFile())) { 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 CmdConfirm.addPending(player, "/plot backup load " + number, 368 () -> backupProfile.restoreBackup(backup, player) 369 .whenComplete((n, error) -> { 370 if (error != null) { 371 player.sendMessage( 372 TranslatableCaption.of("backups.backup_load_failure"), 373 TagResolver.resolver( 374 "reason", 375 Tag.inserting(Component.text(error.getMessage())) 376 ) 377 ); 378 } else { 379 player.sendMessage(TranslatableCaption.of("backups.backup_load_success")); 380 } 381 }) 382 ); 383 } 384 } 385 } 386 }); 387 } 388 } 389 } 390 391}