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