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}