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.backup;
020
021import com.google.common.cache.Cache;
022import com.google.common.cache.CacheBuilder;
023import com.google.inject.Inject;
024import com.google.inject.Singleton;
025import com.plotsquared.core.PlotSquared;
026import com.plotsquared.core.configuration.Settings;
027import com.plotsquared.core.configuration.caption.TranslatableCaption;
028import com.plotsquared.core.inject.factory.PlayerBackupProfileFactory;
029import com.plotsquared.core.player.PlotPlayer;
030import com.plotsquared.core.plot.Plot;
031import com.plotsquared.core.util.task.TaskManager;
032import net.kyori.adventure.text.Component;
033import net.kyori.adventure.text.minimessage.tag.Tag;
034import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
035import org.apache.logging.log4j.LogManager;
036import org.apache.logging.log4j.Logger;
037import org.checkerframework.checker.nullness.qual.NonNull;
038import org.checkerframework.checker.nullness.qual.Nullable;
039
040import java.nio.file.Files;
041import java.nio.file.Path;
042import java.util.Objects;
043import java.util.concurrent.ExecutionException;
044import java.util.concurrent.TimeUnit;
045
046/**
047 * {@inheritDoc}
048 */
049@Singleton
050public class SimpleBackupManager implements BackupManager {
051
052    private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + SimpleBackupManager.class.getSimpleName());
053    private final Path backupPath;
054    private final boolean automaticBackup;
055    private final int backupLimit;
056    private final Cache<PlotCacheKey, BackupProfile> backupProfileCache = CacheBuilder.newBuilder()
057            .expireAfterAccess(3, TimeUnit.MINUTES).build();
058    private final PlayerBackupProfileFactory playerBackupProfileFactory;
059
060    @Inject
061    public SimpleBackupManager(final @NonNull PlayerBackupProfileFactory playerBackupProfileFactory) throws Exception {
062        this.playerBackupProfileFactory = playerBackupProfileFactory;
063        this.backupPath = Objects.requireNonNull(PlotSquared.platform()).getDirectory().toPath().resolve("backups");
064        if (!Files.exists(backupPath)) {
065            Files.createDirectory(backupPath);
066        }
067        this.automaticBackup = Settings.Backup.AUTOMATIC_BACKUPS;
068        this.backupLimit = Settings.Backup.BACKUP_LIMIT;
069    }
070
071    public SimpleBackupManager(
072            final Path backupPath, final boolean automaticBackup,
073            final int backupLimit, final PlayerBackupProfileFactory playerBackupProfileFactory
074    ) {
075        this.backupPath = backupPath;
076        this.automaticBackup = automaticBackup;
077        this.backupLimit = backupLimit;
078        this.playerBackupProfileFactory = playerBackupProfileFactory;
079    }
080
081    @Override
082    public @NonNull BackupProfile getProfile(final @NonNull Plot plot) {
083        if (plot.hasOwner()) {
084            try {
085                return backupProfileCache.get(
086                        new PlotCacheKey(plot),
087                        () -> this.playerBackupProfileFactory.create(plot.getOwnerAbs(), plot)
088                );
089            } catch (ExecutionException e) {
090                final BackupProfile profile = this.playerBackupProfileFactory.create(plot.getOwnerAbs(), plot);
091                this.backupProfileCache.put(new PlotCacheKey(plot), profile);
092                return profile;
093            }
094        }
095        return new NullBackupProfile();
096    }
097
098    @Override
099    public void automaticBackup(@Nullable PlotPlayer<?> player, final @NonNull Plot plot, @NonNull Runnable whenDone) {
100        final BackupProfile profile;
101        if (!this.shouldAutomaticallyBackup() || (profile = getProfile(plot)) instanceof NullBackupProfile) {
102            whenDone.run();
103        } else {
104            if (player != null) {
105                player.sendMessage(
106                        TranslatableCaption.of("backups.backup_automatic_started"),
107                        TagResolver.resolver("plot", Tag.inserting(Component.text(plot.getId().toString())))
108                );
109            }
110            profile.createBackup().whenComplete((backup, throwable) -> {
111                if (throwable != null) {
112                    if (player != null) {
113                        player.sendMessage(
114                                TranslatableCaption.of("backups.backup_automatic_failure"),
115                                TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage())))
116                        );
117                    }
118                    LOGGER.error(
119                            "Error creating backup for plot {};{} and player {}",
120                            plot.getArea(),
121                            plot.getId(),
122                            player == null ? "null" : player.getName(), throwable
123                    );
124                } else {
125                    if (player != null) {
126                        player.sendMessage(TranslatableCaption.of("backups.backup_automatic_finished"));
127                        TaskManager.runTaskAsync(whenDone);
128                    }
129                }
130            });
131        }
132    }
133
134    @Override
135    public boolean shouldAutomaticallyBackup() {
136        return this.automaticBackup;
137    }
138
139    @NonNull
140    public Path getBackupPath() {
141        return this.backupPath;
142    }
143
144    public int getBackupLimit() {
145        return this.backupLimit;
146    }
147
148    private record PlotCacheKey(
149            Plot plot
150    ) {
151
152        @Override
153        public boolean equals(final Object o) {
154            if (this == o) {
155                return true;
156            }
157            if (o == null || getClass() != o.getClass()) {
158                return false;
159            }
160            final PlotCacheKey that = (PlotCacheKey) o;
161            return Objects.equals(plot.getArea(), that.plot.getArea())
162                    && Objects.equals(plot.getId(), that.plot.getId())
163                    && Objects.equals(plot.getOwnerAbs(), that.plot.getOwnerAbs());
164        }
165
166        @Override
167        public int hashCode() {
168            return Objects.hash(plot.getArea(), plot.getId(), plot.getOwnerAbs());
169        }
170
171    }
172
173}