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