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