package com.intellectualcrafters.plot.util.expiry;
import com.google.common.base.Optional;
import com.intellectualcrafters.plot.PS;
import com.intellectualcrafters.plot.config.C;
import com.intellectualcrafters.plot.config.Settings;
import com.intellectualcrafters.plot.database.DBFunc;
import com.intellectualcrafters.plot.flag.FlagManager;
import com.intellectualcrafters.plot.flag.Flags;
import com.intellectualcrafters.plot.generator.HybridUtils;
import com.intellectualcrafters.plot.object.OfflinePlotPlayer;
import com.intellectualcrafters.plot.object.Plot;
import com.intellectualcrafters.plot.object.PlotArea;
import com.intellectualcrafters.plot.object.PlotMessage;
import com.intellectualcrafters.plot.object.PlotPlayer;
import com.intellectualcrafters.plot.object.RunnableVal;
import com.intellectualcrafters.plot.object.RunnableVal3;
import com.intellectualcrafters.plot.util.MainUtil;
import com.intellectualcrafters.plot.util.StringMan;
import com.intellectualcrafters.plot.util.TaskManager;
import com.intellectualcrafters.plot.util.UUIDHandler;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
public class ExpireManager {
public static ExpireManager IMP;
private final ConcurrentHashMap<UUID, Long> dates_cache;
private volatile HashSet<Plot> plotsToDelete;
private ArrayDeque<ExpiryTask> tasks;
/**
* 0 = stopped, 1 = stopping, 2 = running
*/
private int running;
public ExpireManager() {
tasks = new ArrayDeque<>();
dates_cache = new ConcurrentHashMap<>();
}
public void addTask(ExpiryTask task) {
PS.debug("Adding new expiry task!");
this.tasks.add(task);
}
public void handleJoin(PlotPlayer pp) {
storeDate(pp.getUUID(), System.currentTimeMillis());
if (plotsToDelete != null && !plotsToDelete.isEmpty()) {
for (Plot plot : pp.getPlots()) {
plotsToDelete.remove(plot);
}
}
confirmExpiry(pp);
}
public void handleEntry(PlotPlayer pp, Plot plot) {
if (plotsToDelete != null && !plotsToDelete.isEmpty() && pp.hasPermission("plots.admin.command.autoclear") && plotsToDelete.contains(plot)) {
if (!isExpired(new ArrayDeque<>(tasks), plot).isEmpty()) {
confirmExpiry(pp);
} else {
plotsToDelete.remove(plot);
confirmExpiry(pp);
}
}
}
public long getTimestamp(UUID uuid) {
Long value = this.dates_cache.get(uuid);
return value == null ? 0 : value;
}
public void updateExpired(Plot plot) {
if (plotsToDelete != null && !plotsToDelete.isEmpty() && plotsToDelete.contains(plot)) {
if (isExpired(new ArrayDeque<>(tasks), plot).isEmpty()) {
plotsToDelete.remove(plot);
}
}
}
public void confirmExpiry(final PlotPlayer pp) {
if (pp.getMeta("ignoreExpireTask") != null) {
return;
}
if (plotsToDelete != null && !plotsToDelete.isEmpty() && pp.hasPermission("plots.admin.command.autoclear")) {
final int num = plotsToDelete.size();
while (!plotsToDelete.isEmpty()) {
Iterator<Plot> iter = plotsToDelete.iterator();
final Plot current = iter.next();
if (!isExpired(new ArrayDeque<>(tasks), current).isEmpty()) {
TaskManager.runTask(new Runnable() {
@Override
public void run() {
pp.setMeta("ignoreExpireTask", true);
pp.teleport(current.getCenter());
pp.deleteMeta("ignoreExpireTask");
PlotMessage msg = new PlotMessage()
.text(num + " " + (num > 1 ? "plots are" : "plot is") + " expired: ").color("$1").text(current.toString()).color("$2").suggest("/plot list expired")
.tooltip("/plot list expired")
//.text("\n - ").color("$3").text("Delete all (/plot delete expired)").color("$2").command("/plot delete expired")
.text("\n - ").color("$3").text("Delete this (/plot delete)").color("$2").suggest("/plot delete")
.tooltip("/plot delete")
.text("\n - ").color("$3").text("Remind later (/plot set keep 1d)").color("$2").suggest("/plot set keep 1d")
.tooltip("/plot set keep 1d")
.text("\n - ").color("$3").text("Keep this (/plot set keep true)").color("$2").suggest("/plot set keep true")
.tooltip("/plot set keep true");
msg.send(pp);
}
});
return;
} else {
iter.remove();
}
}
plotsToDelete.clear();
}
}
public boolean cancelTask() {
if (this.running != 2) {
return false;
}
this.running = 1;
return true;
}
public boolean runAutomatedTask() {
return runTask(new RunnableVal3<Plot, Runnable, Boolean>() {
@Override
public void run(Plot plot, Runnable runnable, Boolean confirm) {
if (confirm) {
if (plotsToDelete == null) {
plotsToDelete = new HashSet<>();
}
plotsToDelete.add(plot);
runnable.run();
} else {
deleteWithMessage(plot, runnable);
}
}
});
}
public Collection<ExpiryTask> isExpired(ArrayDeque<ExpiryTask> applicable, Plot plot) {
// Filter out invalid worlds
for (int i = 0; i < applicable.size(); i++) {
ExpiryTask et = applicable.poll();
if (et.applies(plot.getArea())) {
applicable.add(et);
}
}
if (applicable.isEmpty()) {
return new ArrayList<>();
}
long diff = getAge(plot);
if (diff == 0) {
return new ArrayList<>();
}
// Filter out non old plots
for (int i = 0; i < applicable.size(); i++) {
ExpiryTask et = applicable.poll();
if (et.applies(diff)) {
applicable.add(et);
}
}
if (applicable.isEmpty()) {
return new ArrayList<>();
}
// Run applicable non confirming tasks
for (int i = 0; i < applicable.size(); i++) {
ExpiryTask expiryTask = applicable.poll();
if (!expiryTask.needsAnalysis() || plot.getArea().TYPE != 0) {
if (!expiryTask.requiresConfirmation()) {
return Collections.singletonList(expiryTask);
}
}
applicable.add(expiryTask);
}
// Run applicable confirming tasks
for (int i = 0; i < applicable.size(); i++) {
ExpiryTask expiryTask = applicable.poll();
if (!expiryTask.needsAnalysis() || plot.getArea().TYPE != 0) {
return Collections.singletonList(expiryTask);
}
applicable.add(expiryTask);
}
return applicable;
}
public ArrayDeque<ExpiryTask> getTasks(PlotArea area) {
ArrayDeque<ExpiryTask> queue = new ArrayDeque<>(tasks);
Iterator<ExpiryTask> iter = queue.iterator();
while (iter.hasNext()) {
if (!iter.next().applies(area)) {
iter.remove();
}
}
return queue;
}
public void passesComplexity(PlotAnalysis analysis, Collection<ExpiryTask> applicable, RunnableVal<Boolean> success, Runnable failure) {
if (analysis != null) {
// Run non confirming tasks
for (ExpiryTask et : applicable) {
if (!et.requiresConfirmation() && et.applies(analysis)) {
success.run(false);
return;
}
}
for (ExpiryTask et : applicable) {
if (et.applies(analysis)) {
success.run(true);
return;
}
}
failure.run();
}
}
public boolean runTask(final RunnableVal3<Plot, Runnable, Boolean> expiredTask) {
if (this.running != 0) {
return false;
}
this.running = 2;
final ConcurrentLinkedDeque<Plot> plots = new ConcurrentLinkedDeque<Plot>(PS.get().getPlots());
TaskManager.runTaskAsync(new Runnable() {
@Override
public void run() {
final Runnable task = this;
if (ExpireManager.this.running != 2) {
ExpireManager.this.running = 0;
return;
}
long start = System.currentTimeMillis();
while (!plots.isEmpty()) {
if (ExpireManager.this.running != 2) {
ExpireManager.this.running = 0;
return;
}
Plot plot = plots.poll();
PlotArea area = plot.getArea();
final Plot newPlot = area.getPlot(plot.getId());
final ArrayDeque<ExpiryTask> applicable = new ArrayDeque<>(tasks);
final Collection<ExpiryTask> expired = isExpired(applicable, newPlot);
if (expired.isEmpty()) {
continue;
}
for (ExpiryTask expiryTask : expired) {
if (!expiryTask.needsAnalysis()) {
expiredTask.run(newPlot, new Runnable() {
@Override
public void run() {
TaskManager.IMP.taskLaterAsync(task, 1);
}
}, expiryTask.requiresConfirmation());
return;
}
}
final RunnableVal<PlotAnalysis> handleAnalysis = new RunnableVal<PlotAnalysis>() {
@Override
public void run(final PlotAnalysis changed) {
passesComplexity(changed, expired, new RunnableVal<Boolean>() {
@Override
public void run(Boolean confirmation) {
expiredTask.run(newPlot, new Runnable() {
@Override
public void run() {
TaskManager.IMP.taskLaterAsync(task, 1);
}
}, confirmation);
}
}, new Runnable() {
@Override
public void run() {
FlagManager.addPlotFlag(newPlot, Flags.ANALYSIS, changed.asList());
TaskManager.runTaskLaterAsync(task, 20);
}
});
}
};
final Runnable doAnalysis = new Runnable() {
@Override
public void run() {
HybridUtils.manager.analyzePlot(newPlot, handleAnalysis);
}
};
PlotAnalysis analysis = newPlot.getComplexity(null);
if (analysis != null) {
passesComplexity(analysis, expired, new RunnableVal<Boolean>() {
@Override
public void run(Boolean value) {
doAnalysis.run();
}
}, new Runnable() {
@Override
public void run() {
TaskManager.IMP.taskLaterAsync(task, 1);
}
});
} else {
doAnalysis.run();
}
return;
}
if (plots.isEmpty()) {
ExpireManager.this.running = 3;
TaskManager.runTaskLater(new Runnable() {
@Override
public void run() {
if (ExpireManager.this.running == 3) {
ExpireManager.this.running = 2;
runTask(expiredTask);
}
}
}, 86400000);
} else {
TaskManager.runTaskLaterAsync(task, 20 * 10);
}
}
});
return true;
}
public void storeDate(UUID uuid, long time) {
this.dates_cache.put(uuid, time);
}
public HashSet<Plot> getPendingExpired() {
return plotsToDelete == null ? new HashSet<Plot>() : plotsToDelete;
}
public void deleteWithMessage(Plot plot, Runnable whenDone) {
if (plot.isMerged()) {
plot.unlinkPlot(true, false);
}
for (UUID helper : plot.getTrusted()) {
PlotPlayer player = UUIDHandler.getPlayer(helper);
if (player != null) {
MainUtil.sendMessage(player, C.PLOT_REMOVED_USER, plot.toString());
}
}
for (UUID helper : plot.getMembers()) {
PlotPlayer player = UUIDHandler.getPlayer(helper);
if (player != null) {
MainUtil.sendMessage(player, C.PLOT_REMOVED_USER, plot.toString());
}
}
Set<Plot> plots = plot.getConnectedPlots();
plot.deletePlot(whenDone);
PlotAnalysis changed = plot.getComplexity(null);
int changes = changed == null ? 0 : changed.changes_sd;
int modified = changed == null ? 0 : changed.changes;
PS.debug("$2[&5Expire&dManager$2] &cDeleted expired plot: " + plot + " User:" + plot.owner + " Delta:" + changes + "/" + modified + " Connected: " + StringMan.getString(plots));
PS.debug("$4 - Area: " + plot.getArea());
if (plot.hasOwner()) {
PS.debug("$4 - Owner: " + UUIDHandler.getName(plot.owner));
} else {
PS.debug("$4 - Owner: Unowned");
}
}
public long getAge(UUID uuid) {
if (UUIDHandler.getPlayer(uuid) != null) {
return 0;
}
String name = UUIDHandler.getName(uuid);
if (name != null) {
Long last = this.dates_cache.get(uuid);
if (last == null) {
OfflinePlotPlayer opp;
if (Settings.UUID.NATIVE_UUID_PROVIDER) {
opp = UUIDHandler.getUUIDWrapper().getOfflinePlayer(uuid);
} else {
opp = UUIDHandler.getUUIDWrapper().getOfflinePlayer(name);
}
if (opp != null && (last = opp.getLastPlayed()) != 0) {
this.dates_cache.put(uuid, last);
} else {
return 0;
}
}
if (last == 0) {
return 0;
}
return System.currentTimeMillis() - last;
}
return 0;
}
public long getAge(Plot plot) {
if (!plot.hasOwner() || Objects.equals(DBFunc.everyone, plot.owner) || UUIDHandler.getPlayer(plot.owner) != null || plot.getRunning() > 0) {
return 0;
}
Optional<?> keep = plot.getFlag(Flags.KEEP);
if (keep.isPresent()) {
Object value = keep.get();
if (value instanceof Boolean) {
if (Boolean.TRUE.equals(value)) {
return 0;
}
} else if (value instanceof Long) {
if ((Long) value > System.currentTimeMillis()) {
return 0;
}
} else { // Invalid?
return 0;
}
}
long min = Long.MAX_VALUE;
for (UUID owner : plot.getOwners()) {
long age = getAge(owner);
if (age < min) {
min = age;
}
}
return min;
}
}