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.PlotSquared;
023import com.plotsquared.core.configuration.caption.StaticCaption;
024import com.plotsquared.core.configuration.caption.TranslatableCaption;
025import com.plotsquared.core.configuration.file.YamlConfiguration;
026import com.plotsquared.core.database.DBFunc;
027import com.plotsquared.core.database.Database;
028import com.plotsquared.core.database.MySQL;
029import com.plotsquared.core.database.SQLManager;
030import com.plotsquared.core.database.SQLite;
031import com.plotsquared.core.inject.annotations.WorldConfig;
032import com.plotsquared.core.listener.PlotListener;
033import com.plotsquared.core.player.PlotPlayer;
034import com.plotsquared.core.plot.Plot;
035import com.plotsquared.core.plot.PlotArea;
036import com.plotsquared.core.plot.PlotId;
037import com.plotsquared.core.plot.world.PlotAreaManager;
038import com.plotsquared.core.plot.world.SinglePlotArea;
039import com.plotsquared.core.util.EventDispatcher;
040import com.plotsquared.core.util.FileUtils;
041import com.plotsquared.core.util.query.PlotQuery;
042import com.plotsquared.core.util.task.TaskManager;
043import net.kyori.adventure.text.minimessage.Template;
044import org.checkerframework.checker.nullness.qual.NonNull;
045
046import java.io.File;
047import java.sql.SQLException;
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.HashMap;
051import java.util.List;
052import java.util.Map.Entry;
053
054@CommandDeclaration(command = "database",
055        aliases = {"convert"},
056        category = CommandCategory.ADMINISTRATION,
057        permission = "plots.database",
058        requiredType = RequiredType.CONSOLE,
059        usage = "/plot database [area] <sqlite | mysql | import>")
060public class DatabaseCommand extends SubCommand {
061
062    private final PlotAreaManager plotAreaManager;
063    private final EventDispatcher eventDispatcher;
064    private final PlotListener plotListener;
065    private final YamlConfiguration worldConfiguration;
066
067    @Inject
068    public DatabaseCommand(
069            final @NonNull PlotAreaManager plotAreaManager,
070            final @NonNull EventDispatcher eventDispatcher,
071            final @NonNull PlotListener plotListener,
072            @WorldConfig final @NonNull YamlConfiguration worldConfiguration
073    ) {
074        this.plotAreaManager = plotAreaManager;
075        this.eventDispatcher = eventDispatcher;
076        this.plotListener = plotListener;
077        this.worldConfiguration = worldConfiguration;
078    }
079
080    public static void insertPlots(
081            final SQLManager manager, final List<Plot> plots,
082            final PlotPlayer<?> player
083    ) {
084        TaskManager.runTaskAsync(() -> {
085            try {
086                ArrayList<Plot> ps = new ArrayList<>(plots);
087                player.sendMessage(TranslatableCaption.of("database.starting_conversion"));
088                manager.createPlotsAndData(ps, () -> {
089                    player.sendMessage(TranslatableCaption.of("database.conversion_done"));
090                    manager.close();
091                });
092            } catch (Exception e) {
093                player.sendMessage(TranslatableCaption.of("database.conversion_failed"));
094                e.printStackTrace();
095            }
096        });
097    }
098
099    @Override
100    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
101        if (args.length < 1) {
102            player.sendMessage(
103                    TranslatableCaption.of("commandconfig.command_syntax"),
104                    Template.of("value", "/plot database [area] <sqlite | mysql | import>")
105            );
106            return false;
107        }
108        List<Plot> plots;
109        PlotArea area = this.plotAreaManager.getPlotAreaByString(args[0]);
110        if (area != null) {
111            plots = PlotSquared.get().sortPlotsByTemp(area.getPlots());
112            args = Arrays.copyOfRange(args, 1, args.length);
113        } else {
114            plots = PlotSquared.get().sortPlotsByTemp(PlotQuery.newQuery().allPlots().asList());
115        }
116        if (args.length < 1) {
117            player.sendMessage(
118                    TranslatableCaption.of("commandconfig.command_syntax"),
119                    Template.of("value", "/plot database [area] <sqlite|mysql|import>")
120            );
121            player.sendMessage(TranslatableCaption.of("database.arg"));
122            return false;
123        }
124        try {
125            Database implementation;
126            String prefix = "";
127            switch (args[0].toLowerCase()) {
128                case "import" -> {
129                    if (args.length < 2) {
130                        player.sendMessage(
131                                TranslatableCaption.of("commandconfig.command_syntax"),
132                                Template.of("value", "/plot database import <sqlite file> [prefix]")
133                        );
134                        return false;
135                    }
136                    File file = FileUtils.getFile(
137                            PlotSquared.platform().getDirectory(),
138                            args[1].endsWith(".db") ? args[1] : args[1] + ".db"
139                    );
140                    if (!file.exists()) {
141                        player.sendMessage(
142                                TranslatableCaption.of("database.does_not_exist"),
143                                Template.of("value", String.valueOf(file))
144                        );
145                        return false;
146                    }
147                    player.sendMessage(TranslatableCaption.of("database.starting_conversion"));
148                    implementation = new SQLite(file);
149                    SQLManager manager = new SQLManager(implementation, args.length == 3 ? args[2] : "",
150                            this.eventDispatcher, this.plotListener, this.worldConfiguration
151                    );
152                    HashMap<String, HashMap<PlotId, Plot>> map = manager.getPlots();
153                    plots = new ArrayList<>();
154                    for (Entry<String, HashMap<PlotId, Plot>> entry : map.entrySet()) {
155                        String areaName = entry.getKey();
156                        PlotArea pa = this.plotAreaManager.getPlotAreaByString(areaName);
157                        if (pa != null) {
158                            for (Entry<PlotId, Plot> entry2 : entry.getValue().entrySet()) {
159                                Plot plot = entry2.getValue();
160                                if (pa.getOwnedPlotAbs(plot.getId()) != null) {
161                                    if (pa instanceof SinglePlotArea) {
162                                        Plot newPlot = pa.getNextFreePlot(null, plot.getId());
163                                        if (newPlot != null) {
164                                            PlotId newId = newPlot.getId();
165                                            PlotId id = plot.getId();
166                                            File worldFile =
167                                                    new File(
168                                                            PlotSquared.platform().worldContainer(),
169                                                            id.toCommaSeparatedString()
170                                                    );
171                                            if (worldFile.exists()) {
172                                                File newFile =
173                                                        new File(
174                                                                PlotSquared.platform().worldContainer(),
175                                                                newId.toCommaSeparatedString()
176                                                        );
177                                                worldFile.renameTo(newFile);
178                                            }
179                                            plot.setId(newId);
180                                            plot.setArea(pa);
181                                            plots.add(plot);
182                                            continue;
183                                        }
184                                    }
185                                    player.sendMessage(
186                                            TranslatableCaption.of("database.skipping_duplicated_plot"),
187                                            Template.of("plot", String.valueOf(plot)),
188                                            Template.of("id", String.valueOf(plot.temp))
189                                    );
190                                    continue;
191                                }
192                                plot.setArea(pa);
193                                plots.add(plot);
194                            }
195                        } else {
196                            HashMap<PlotId, Plot> plotMap = PlotSquared.get().plots_tmp
197                                    .computeIfAbsent(areaName, k -> new HashMap<>());
198                            plotMap.putAll(entry.getValue());
199                        }
200                    }
201                    DBFunc.createPlotsAndData(
202                            plots,
203                            () -> player.sendMessage(TranslatableCaption.of("database.conversion_done"))
204                    );
205                    return true;
206                }
207                case "mysql" -> {
208                    if (args.length < 6) {
209                        player.sendMessage(StaticCaption.of(
210                                "/plot database mysql [host] [port] [username] [password] [database] {prefix}"));
211                        return false;
212                    }
213                    String host = args[1];
214                    String port = args[2];
215                    String username = args[3];
216                    String password = args[4];
217                    String database = args[5];
218                    if (args.length > 6) {
219                        prefix = args[6];
220                    }
221                    implementation = new MySQL(host, port, database, username, password);
222                }
223                case "sqlite" -> {
224                    if (args.length < 2) {
225                        player.sendMessage(StaticCaption.of("/plot database sqlite [file]"));
226                        return false;
227                    }
228                    File sqliteFile =
229                            FileUtils.getFile(PlotSquared.platform().getDirectory(), args[1] + ".db");
230                    implementation = new SQLite(sqliteFile);
231                }
232                default -> {
233                    player.sendMessage(StaticCaption.of("/plot database [sqlite/mysql]"));
234                    return false;
235                }
236            }
237            try {
238                SQLManager manager = new SQLManager(
239                        implementation,
240                        prefix,
241                        this.eventDispatcher,
242                        this.plotListener,
243                        this.worldConfiguration
244                );
245                DatabaseCommand.insertPlots(manager, plots, player);
246                return true;
247            } catch (ClassNotFoundException | SQLException e) {
248                player.sendMessage(TranslatableCaption.of("database.failed_to_save_plots"));
249                player.sendMessage(TranslatableCaption.of("errors.stacktrace_begin"));
250                e.printStackTrace();
251                player.sendMessage(TranslatableCaption.of("errors.stacktrace_end"));
252                player.sendMessage(TranslatableCaption.of("database.invalid_args"));
253                return false;
254            }
255        } catch (ClassNotFoundException | SQLException e) {
256            player.sendMessage(TranslatableCaption.of("database.failed_to_open"));
257            player.sendMessage(TranslatableCaption.of("errors.stacktrace_begin"));
258            e.printStackTrace();
259            player.sendMessage(TranslatableCaption.of("errors.stacktrace_end"));
260            player.sendMessage(TranslatableCaption.of("database.invalid_args"));
261            return false;
262        }
263    }
264
265}