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.ConfigurationNode;
024import com.plotsquared.core.configuration.ConfigurationSection;
025import com.plotsquared.core.configuration.ConfigurationUtil;
026import com.plotsquared.core.configuration.InvalidConfigurationException;
027import com.plotsquared.core.configuration.Settings;
028import com.plotsquared.core.configuration.caption.TranslatableCaption;
029import com.plotsquared.core.configuration.file.YamlConfiguration;
030import com.plotsquared.core.events.TeleportCause;
031import com.plotsquared.core.inject.annotations.WorldConfig;
032import com.plotsquared.core.inject.annotations.WorldFile;
033import com.plotsquared.core.permissions.Permission;
034import com.plotsquared.core.player.PlotPlayer;
035import com.plotsquared.core.plot.PlotArea;
036import com.plotsquared.core.plot.PlotManager;
037import com.plotsquared.core.plot.world.PlotAreaManager;
038import com.plotsquared.core.setup.PlotAreaBuilder;
039import com.plotsquared.core.setup.SettingsNodesWrapper;
040import com.plotsquared.core.util.FileBytes;
041import com.plotsquared.core.util.FileUtils;
042import com.plotsquared.core.util.SetupUtils;
043import com.plotsquared.core.util.TabCompletions;
044import com.plotsquared.core.util.WorldUtil;
045import com.plotsquared.core.util.task.TaskManager;
046import org.checkerframework.checker.nullness.qual.NonNull;
047
048import java.io.File;
049import java.io.FileInputStream;
050import java.io.FileOutputStream;
051import java.io.IOException;
052import java.util.Collection;
053import java.util.Collections;
054import java.util.LinkedList;
055import java.util.List;
056import java.util.Set;
057import java.util.stream.Collectors;
058import java.util.zip.ZipEntry;
059import java.util.zip.ZipInputStream;
060import java.util.zip.ZipOutputStream;
061
062@CommandDeclaration(command = "template",
063        permission = "plots.admin",
064        usage = "/plot template [import | export] <world> <template>",
065        category = CommandCategory.ADMINISTRATION)
066public class Template extends SubCommand {
067
068    private final PlotAreaManager plotAreaManager;
069    private final YamlConfiguration worldConfiguration;
070    private final File worldFile;
071    private final SetupUtils setupUtils;
072    private final WorldUtil worldUtil;
073
074    @Inject
075    public Template(
076            final @NonNull PlotAreaManager plotAreaManager,
077            @WorldConfig final @NonNull YamlConfiguration worldConfiguration,
078            @WorldFile final @NonNull File worldFile,
079            final @NonNull SetupUtils setupUtils,
080            final @NonNull WorldUtil worldUtil
081    ) {
082        this.plotAreaManager = plotAreaManager;
083        this.worldConfiguration = worldConfiguration;
084        this.worldFile = worldFile;
085        this.setupUtils = setupUtils;
086        this.worldUtil = worldUtil;
087    }
088
089    public static boolean extractAllFiles(String world, String template) {
090        try {
091            File folder =
092                    FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES);
093            if (!folder.exists()) {
094                return false;
095            }
096            File output = PlotSquared.platform().getDirectory();
097            if (!output.exists()) {
098                output.mkdirs();
099            }
100            File input = new File(folder + File.separator + template + ".template");
101            try (ZipInputStream zis = new ZipInputStream(new FileInputStream(input))) {
102                ZipEntry ze = zis.getNextEntry();
103                byte[] buffer = new byte[2048];
104                while (ze != null) {
105                    if (!ze.isDirectory()) {
106                        String name = ze.getName().replace('\\', File.separatorChar)
107                                .replace('/', File.separatorChar);
108                        File newFile = new File(
109                                (output + File.separator + name).replaceAll("__TEMP_DIR__", world));
110                        File parent = newFile.getParentFile();
111                        if (parent != null) {
112                            parent.mkdirs();
113                        }
114                        try (FileOutputStream fos = new FileOutputStream(newFile)) {
115                            int len;
116                            while ((len = zis.read(buffer)) > 0) {
117                                fos.write(buffer, 0, len);
118                            }
119                        }
120                    }
121                    ze = zis.getNextEntry();
122                }
123                zis.closeEntry();
124            }
125            return true;
126        } catch (IOException e) {
127            e.printStackTrace();
128            return false;
129        }
130    }
131
132    public static byte[] getBytes(PlotArea plotArea) {
133        ConfigurationSection section = PlotSquared
134                .get()
135                .getWorldConfiguration()
136                .getConfigurationSection("worlds." + plotArea.getWorldName());
137        YamlConfiguration config = new YamlConfiguration();
138        String generator = PlotSquared.platform().setupUtils().getGenerator(plotArea);
139        if (generator != null) {
140            config.set("generator.plugin", generator);
141        }
142        for (String key : section.getKeys(true)) {
143            config.set(key, section.get(key));
144        }
145        return config.saveToString().getBytes();
146    }
147
148    public static void zipAll(String world, Set<FileBytes> files) throws IOException {
149        File output = FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES);
150        output.mkdirs();
151        try (FileOutputStream fos = new FileOutputStream(
152                output + File.separator + world + ".template");
153             ZipOutputStream zos = new ZipOutputStream(fos)) {
154
155            for (FileBytes file : files) {
156                ZipEntry ze = new ZipEntry(file.path);
157                zos.putNextEntry(ze);
158                zos.write(file.data);
159            }
160            zos.closeEntry();
161        }
162    }
163
164    @Override
165    public boolean onCommand(final PlotPlayer<?> player, String[] args) {
166        if (args.length != 2 && args.length != 3) {
167            if (args.length == 1) {
168                if (args[0].equalsIgnoreCase("export")) {
169                    player.sendMessage(
170                            TranslatableCaption.of("commandconfig.command_syntax"),
171                            net.kyori.adventure.text.minimessage.Template.of("value", "/plot template export <world>")
172                    );
173                    return true;
174                } else if (args[0].equalsIgnoreCase("import")) {
175                    player.sendMessage(
176                            TranslatableCaption.of("commandconfig.command_syntax"),
177                            net.kyori.adventure.text.minimessage.Template.of("value", "/plot template import <world> <template>")
178                    );
179                    return true;
180                }
181            }
182            sendUsage(player);
183            return true;
184        }
185        final String world = args[1];
186        switch (args[0].toLowerCase()) {
187            case "import" -> {
188                if (args.length != 3) {
189                    player.sendMessage(
190                            TranslatableCaption.of("commandconfig.command_syntax"),
191                            net.kyori.adventure.text.minimessage.Template.of("value", "/plot template import <world> <template>")
192                    );
193                    return false;
194                }
195                if (this.plotAreaManager.hasPlotArea(world)) {
196                    player.sendMessage(
197                            TranslatableCaption.of("setup.setup_world_taken"),
198                            net.kyori.adventure.text.minimessage.Template.of("value", world)
199                    );
200                    return false;
201                }
202                boolean result = extractAllFiles(world, args[2]);
203                if (!result) {
204                    player.sendMessage(
205                            TranslatableCaption.of("template.invalid_template"),
206                            net.kyori.adventure.text.minimessage.Template.of("value", args[2])
207                    );
208                    return false;
209                }
210                File worldFile = FileUtils.getFile(
211                        PlotSquared.platform().getDirectory(),
212                        Settings.Paths.TEMPLATES + File.separator + "tmp-data.yml"
213                );
214                YamlConfiguration worldConfig = YamlConfiguration.loadConfiguration(worldFile);
215                this.worldConfiguration.set("worlds." + world, worldConfig.get(""));
216                try {
217                    this.worldConfiguration.save(this.worldFile);
218                    this.worldConfiguration.load(this.worldFile);
219                } catch (InvalidConfigurationException | IOException e) {
220                    e.printStackTrace();
221                }
222                String manager =
223                        worldConfig.getString("generator.plugin", PlotSquared.platform().pluginName());
224                String generator = worldConfig.getString("generator.init", manager);
225                PlotAreaBuilder builder = PlotAreaBuilder.newBuilder()
226                        .plotAreaType(ConfigurationUtil.getType(worldConfig))
227                        .terrainType(ConfigurationUtil.getTerrain(worldConfig))
228                        .plotManager(manager)
229                        .generatorName(generator)
230                        .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null))
231                        .worldName(world);
232
233                this.setupUtils.setupWorld(builder);
234                TaskManager.runTask(() -> {
235                    player.teleport(this.worldUtil.getSpawn(world), TeleportCause.COMMAND_TEMPLATE);
236                    player.sendMessage(TranslatableCaption.of("setup.setup_finished"));
237                });
238                return true;
239            }
240            case "export" -> {
241                if (args.length != 2) {
242                    player.sendMessage(
243                            TranslatableCaption.of("commandconfig.command_syntax"),
244                            net.kyori.adventure.text.minimessage.Template.of("value", "/plot template export <world>")
245                    );
246                    return false;
247                }
248                final PlotArea area = this.plotAreaManager.getPlotAreaByString(world);
249                if (area == null) {
250                    player.sendMessage(
251                            TranslatableCaption.of("errors.not_valid_plot_world"),
252                            net.kyori.adventure.text.minimessage.Template.of("value", args[1])
253                    );
254                    return false;
255                }
256                final PlotManager manager = area.getPlotManager();
257                TaskManager.runTaskAsync(() -> {
258                    try {
259                        manager.exportTemplate();
260                    } catch (Exception e) { // Must recover from any exception thrown a third party template manager
261                        e.printStackTrace();
262                        player.sendMessage(
263                                TranslatableCaption.of("template.template_failed"),
264                                net.kyori.adventure.text.minimessage.Template.of("value", e.getMessage())
265                        );
266                        return;
267                    }
268                    player.sendMessage(TranslatableCaption.of("setup.setup_finished"));
269                });
270                return true;
271            }
272            default -> sendUsage(player);
273        }
274        return false;
275    }
276
277    @Override
278    public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) {
279        if (args.length == 1) {
280            final List<String> completions = new LinkedList<>();
281            if (player.hasPermission(Permission.PERMISSION_TEMPLATE_EXPORT)) {
282                completions.add("export");
283            }
284            if (player.hasPermission(Permission.PERMISSION_TEMPLATE_IMPORT)) {
285                completions.add("import");
286            }
287            final List<Command> commands = completions.stream().filter(completion -> completion
288                            .toLowerCase()
289                            .startsWith(args[0].toLowerCase()))
290                    .map(completion -> new Command(
291                            null,
292                            true,
293                            completion,
294                            "",
295                            RequiredType.NONE,
296                            CommandCategory.ADMINISTRATION
297                    ) {
298                    }).collect(Collectors.toCollection(LinkedList::new));
299            if (player.hasPermission(Permission.PERMISSION_TEMPLATE) && args[0].length() > 0) {
300                commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList()));
301            }
302            return commands;
303        }
304        return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList());
305    }
306
307}