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