diff --git a/plugin.yml b/plugin.yml index 5e18afe9..d7087f84 100644 --- a/plugin.yml +++ b/plugin.yml @@ -18,6 +18,7 @@ permissions: massivecore.store.stats: {description: show mstore statistics, default: false} massivecore.store.listcolls: {description: list collections in a database, default: false} massivecore.store.copydb: {description: copy database content, default: false} + massivecore.store.playerclean: {description: clean inactive players, default: false} massivecore.usys: {description: use the usys command, default: false} massivecore.usys.multiverse: {description: manage multiverses, default: false} massivecore.usys.multiverse.list: {description: list multiverses, default: false} @@ -62,6 +63,7 @@ permissions: massivecore.store.stats: true massivecore.store.listcolls: true massivecore.store.copydb: true + massivecore.store.playerclean: true massivecore.usys: true massivecore.usys.multiverse: true massivecore.usys.multiverse.list: true @@ -101,6 +103,7 @@ permissions: massivecore.kit.rank3: default: false children: + massivecore.store.playerclean: true massivecore.kit.rank2: true massivecore.kit.rank2: default: false diff --git a/src/com/massivecraft/massivecore/MassiveCoreMConf.java b/src/com/massivecraft/massivecore/MassiveCoreMConf.java index 523baef3..be24bd33 100644 --- a/src/com/massivecraft/massivecore/MassiveCoreMConf.java +++ b/src/com/massivecraft/massivecore/MassiveCoreMConf.java @@ -127,6 +127,27 @@ public class MassiveCoreMConf extends Entity @EditorType(TypeBooleanOn.class) public boolean warnOnLocalAlter = false; + // -------------------------------------------- // + // PLAYERCLEAN + // -------------------------------------------- // + + // How often should the task run? + // When set to 0 this feature is disabled. Meaning no cleaning will be done. + // Default: 1 day (Per default once a day.) + public long playercleanPeriodMillis = TimeUnit.MILLIS_PER_DAY; + + // This is used to decide at what time of the day the task will run. + // For Example: If the taskPeriodMillis is 24 hours: + // Set it to 0 for UTC midnight. + // Set it to 3600000 for UTC midnight + 1 hour. + public long playercleanOffsetMillis = 0; + + // When did the task last run? + // This need not be modified by the server owner. + // It will be set for you automatically. + // 0 means it never ran before. + public long playercleanLastMillis = 0; + // -------------------------------------------- // // MONGODB // -------------------------------------------- // diff --git a/src/com/massivecraft/massivecore/MassiveCorePerm.java b/src/com/massivecraft/massivecore/MassiveCorePerm.java index 95fe23b1..80310634 100644 --- a/src/com/massivecraft/massivecore/MassiveCorePerm.java +++ b/src/com/massivecraft/massivecore/MassiveCorePerm.java @@ -18,6 +18,7 @@ public enum MassiveCorePerm implements Identified STORE_STATS, STORE_LISTCOLLS, STORE_COPYDB, + STORE_PLAYERCLEAN, USYS, USYS_MULTIVERSE, USYS_MULTIVERSE_LIST, diff --git a/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStore.java b/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStore.java index d3276d4e..f4c0a7a3 100755 --- a/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStore.java +++ b/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStore.java @@ -20,5 +20,6 @@ public class CmdMassiveCoreStore extends MassiveCoreCommand public CmdMassiveCoreStoreStats cmdMassiveCoreStoreStats = new CmdMassiveCoreStoreStats(); public CmdMassiveCoreStoreListcolls cmdMassiveCoreStoreListcolls = new CmdMassiveCoreStoreListcolls(); public CmdMassiveCoreStoreCopydb cmdMassiveCoreStoreCopydb = new CmdMassiveCoreStoreCopydb(); + public CmdMassiveCoreStorePlayerclean cmdMassiveCoreStorePlayerclean = new CmdMassiveCoreStorePlayerclean(); } diff --git a/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStorePlayerclean.java b/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStorePlayerclean.java new file mode 100755 index 00000000..480aacd9 --- /dev/null +++ b/src/com/massivecraft/massivecore/cmd/CmdMassiveCoreStorePlayerclean.java @@ -0,0 +1,43 @@ +package com.massivecraft.massivecore.cmd; + +import com.massivecraft.massivecore.MassiveException; +import com.massivecraft.massivecore.command.type.container.TypeSet; +import com.massivecraft.massivecore.command.type.store.TypeSenderColl; +import com.massivecraft.massivecore.store.SenderColl; +import com.massivecraft.massivecore.store.inactive.InactiveUtil; +import com.massivecraft.massivecore.util.IdUtil; +import com.massivecraft.massivecore.util.MUtil; +import org.bukkit.command.CommandSender; + +import java.util.Set; + +public class CmdMassiveCoreStorePlayerclean extends MassiveCoreCommand +{ + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + public CmdMassiveCoreStorePlayerclean() + { + // Parameters + this.addParameter(TypeSet.get(TypeSenderColl.get()), "player coll", true).setDesc("the coll to clean inactive players from"); + } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public void perform() throws MassiveException + { + Set> colls = this.readArg(); + + Set receivers = MUtil.set(sender, IdUtil.getConsole()); + + for (SenderColl coll : colls) + { + InactiveUtil.considerRemoveInactive(coll, receivers); + } + } + +} diff --git a/src/com/massivecraft/massivecore/command/type/store/TypeSenderColl.java b/src/com/massivecraft/massivecore/command/type/store/TypeSenderColl.java new file mode 100644 index 00000000..a8719b7c --- /dev/null +++ b/src/com/massivecraft/massivecore/command/type/store/TypeSenderColl.java @@ -0,0 +1,29 @@ +package com.massivecraft.massivecore.command.type.store; + +import com.massivecraft.massivecore.command.type.TypeAbstractChoice; +import com.massivecraft.massivecore.store.Coll; +import com.massivecraft.massivecore.store.SenderColl; + +import java.util.Collection; + +public class TypeSenderColl extends TypeAbstractChoice> +{ + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + private static TypeSenderColl i = new TypeSenderColl(); + public static TypeSenderColl get() { return i; } + public TypeSenderColl() { super(Coll.class); } + + // -------------------------------------------- // + // OVERRIDE + // -------------------------------------------- // + + @Override + public Collection> getAll() + { + return SenderColl.getSenderInstances(); + } + +} diff --git a/src/com/massivecraft/massivecore/engine/EngineMassiveCorePlayerclean.java b/src/com/massivecraft/massivecore/engine/EngineMassiveCorePlayerclean.java new file mode 100644 index 00000000..69154afd --- /dev/null +++ b/src/com/massivecraft/massivecore/engine/EngineMassiveCorePlayerclean.java @@ -0,0 +1,93 @@ +package com.massivecraft.massivecore.engine; + +import com.massivecraft.massivecore.Engine; +import com.massivecraft.massivecore.MassiveCore; +import com.massivecraft.massivecore.MassiveCoreMConf; +import com.massivecraft.massivecore.event.EventMassiveCorePlayercleanToleranceMillis; +import com.massivecraft.massivecore.store.SenderColl; +import com.massivecraft.massivecore.store.inactive.InactiveUtil; +import com.massivecraft.massivecore.util.IdUtil; +import org.bukkit.command.CommandSender; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; + +import java.util.Collections; +import java.util.List; + +public class EngineMassiveCorePlayerclean extends Engine +{ + // -------------------------------------------- // + // INSTANCE & CONSTRUCT + // -------------------------------------------- // + + private static EngineMassiveCorePlayerclean i = new EngineMassiveCorePlayerclean(); + public static EngineMassiveCorePlayerclean get() { return i; } + public EngineMassiveCorePlayerclean() + { + // Just check once a minute + this.setPeriod(60L * 20L); + } + + // -------------------------------------------- // + // OVERRIDE: RUNNABLE + // -------------------------------------------- // + + @Override + public void run() + { + final long now = System.currentTimeMillis(); + + // If this is the right server ... + if ( ! MassiveCore.isTaskServer()) return; + + // ... and the current invocation ... + final long currentInvocation = getInvocationFromMillis(now); + + // ... and the last invocation ... + final long lastInvocation = getInvocationFromMillis(MassiveCoreMConf.get().playercleanLastMillis); + + // ... are different ... + if (currentInvocation == lastInvocation) return; + + // ... then it's time to invoke. + invoke(now); + } + + public void invoke(long now) + { + // Update lastMillis + MassiveCoreMConf.get().playercleanLastMillis = now; + MassiveCoreMConf.get().changed(); + + List recipients = Collections.singletonList(IdUtil.getConsole()); + for (SenderColl coll : SenderColl.getSenderInstances()) + { + if (!coll.isPlayercleanTaskEnabled()) continue; + InactiveUtil.considerRemoveInactive(now, coll, recipients); + } + } + + // -------------------------------------------- // + // TASK MILLIS AND INVOCATION + // -------------------------------------------- // + // The invocation is the amount of periods from UNIX time to now. + // It will increment by one when a period has passed. + + // Remember to check isDisabled first! + // Here we accept millis from inside the period by rounding down. + private static long getInvocationFromMillis(long millis) + { + return (millis - MassiveCoreMConf.get().playercleanOffsetMillis) / MassiveCoreMConf.get().playercleanPeriodMillis; + } + + // -------------------------------------------- // + // DEFAULT + // -------------------------------------------- // + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void defaultMillis(EventMassiveCorePlayercleanToleranceMillis event) + { + event.getToleranceCauseMillis().put("Default", event.getColl().getPlayercleanToleranceMillis()); + } + +} diff --git a/src/com/massivecraft/massivecore/event/EventMassiveCorePlayercleanToleranceMillis.java b/src/com/massivecraft/massivecore/event/EventMassiveCorePlayercleanToleranceMillis.java new file mode 100644 index 00000000..33571839 --- /dev/null +++ b/src/com/massivecraft/massivecore/event/EventMassiveCorePlayercleanToleranceMillis.java @@ -0,0 +1,79 @@ +package com.massivecraft.massivecore.event; + +import com.massivecraft.massivecore.store.SenderColl; +import com.massivecraft.massivecore.store.SenderEntity; +import com.massivecraft.massivecore.store.inactive.Inactive; +import org.bukkit.event.HandlerList; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class EventMassiveCorePlayercleanToleranceMillis extends EventMassiveCore +{ + // -------------------------------------------- // + // REQUIRED EVENT CODE + // -------------------------------------------- // + + private static final HandlerList handlers = new HandlerList(); + @Override public HandlerList getHandlers() { return handlers; } + public static HandlerList getHandlerList() { return handlers; } + + // -------------------------------------------- // + // FIELD + // -------------------------------------------- // + + private final long now; + public long getNow() { return now; } + + private final SenderEntity entity; + public SenderEntity getEntity() { return this.entity; } + + public SenderColl getColl() { return entity.getColl(); } + + private final Map toleranceCauseMillis = new LinkedHashMap<>(); + public Map getToleranceCauseMillis() { return this.toleranceCauseMillis; } + + // -------------------------------------------- // + // CONSTRUCT + // -------------------------------------------- // + + public EventMassiveCorePlayercleanToleranceMillis(SenderEntity entity) + { + this(System.currentTimeMillis(), entity); + } + + public EventMassiveCorePlayercleanToleranceMillis(long now, SenderEntity entity) + { + this.now = now; + this.entity = entity; + } + + // -------------------------------------------- // + // CONVENIENCE + // -------------------------------------------- // + + public long getToleranceMillis() + { + long ret = 0; + + for (Long value : toleranceCauseMillis.values()) + { + ret += value; + } + + return ret; + } + + public boolean shouldBeRemoved() + { + long toleranceMillis = getToleranceMillis(); + + long now = this.getNow(); + long lastActivityMillis = ((Inactive)this.getEntity()).getLastActivityMillis(); + + long removeTime = lastActivityMillis + toleranceMillis; + + return now >= removeTime; + } + +} diff --git a/src/com/massivecraft/massivecore/store/SenderColl.java b/src/com/massivecraft/massivecore/store/SenderColl.java index e0ccf9d2..f28029a3 100644 --- a/src/com/massivecraft/massivecore/store/SenderColl.java +++ b/src/com/massivecraft/massivecore/store/SenderColl.java @@ -16,6 +16,13 @@ import java.util.List; public class SenderColl> extends Coll implements SenderIdSource { + // This should be false under most circumstances. + // In some cases such as Factions we want it though. + // Especially so we don't change the years old way Factions does it. + private boolean playercleanTaskEnabled = false; + public boolean isPlayercleanTaskEnabled() { return this.playercleanTaskEnabled; } + public void setPlayercleanTaskEnabled(boolean playercleanTaskEnabled) { this.playercleanTaskEnabled = playercleanTaskEnabled; } + // -------------------------------------------- // // CONSTRUCT // -------------------------------------------- // @@ -194,5 +201,15 @@ public class SenderColl> extends Coll implements Se senderColl.setSenderReference(senderId, sender); } } + + // -------------------------------------------- // + // ACTIVITY MILLIS + // -------------------------------------------- // + // Must be overriden if they want to use it. + public long getPlayercleanToleranceMillis() + { + return -1; + } + } diff --git a/src/com/massivecraft/massivecore/store/inactive/Inactive.java b/src/com/massivecraft/massivecore/store/inactive/Inactive.java new file mode 100644 index 00000000..a4f8e6c0 --- /dev/null +++ b/src/com/massivecraft/massivecore/store/inactive/Inactive.java @@ -0,0 +1,6 @@ +package com.massivecraft.massivecore.store.inactive; + +public interface Inactive +{ + long getLastActivityMillis(); +} diff --git a/src/com/massivecraft/massivecore/store/inactive/InactiveUtil.java b/src/com/massivecraft/massivecore/store/inactive/InactiveUtil.java new file mode 100644 index 00000000..70a04947 --- /dev/null +++ b/src/com/massivecraft/massivecore/store/inactive/InactiveUtil.java @@ -0,0 +1,79 @@ +package com.massivecraft.massivecore.store.inactive; + +import com.massivecraft.massivecore.event.EventMassiveCorePlayercleanToleranceMillis; +import com.massivecraft.massivecore.mixin.MixinMessage; +import com.massivecraft.massivecore.store.SenderColl; +import com.massivecraft.massivecore.store.SenderEntity; +import com.massivecraft.massivecore.util.Txt; +import org.bukkit.command.CommandSender; + +import java.util.Collection; + +public class InactiveUtil +{ + public static void considerRemoveInactive(final SenderColl> coll, final Iterable recipients) + { + considerRemoveInactive(System.currentTimeMillis(), coll, recipients); + } + + public static void considerRemoveInactive(long now, final SenderColl> coll, final Iterable recipients) + { + final long playercleanToleranceMillis = coll.getPlayercleanToleranceMillis(); + if (playercleanToleranceMillis <= 0) + { + for (CommandSender recipient : recipients) + { + MixinMessage.get().msgOne(recipient, "%s does not support player cleaning.", coll.getName()); + } + return; + } + + final long start = System.currentTimeMillis(); + int count = 0; + + final Collection> senderEntitiesOffline = coll.getAllOffline(); + + // For each offline player ... + for (SenderEntity entity : senderEntitiesOffline) + { + // ... see if they should be removed. + boolean result = considerRemoveInactive(now, entity, recipients); + if (result) count++; + } + + long time = System.currentTimeMillis() - start; + for (CommandSender recipient : recipients) + { + MixinMessage.get().msgOne(recipient, "Removed %d players from %s took %dms.", count, coll.getName(), time); + } + } + + public static boolean considerRemoveInactive(long now, SenderEntity entity, Iterable recipients) + { + if ( ! (entity instanceof Inactive)) return false; + if (entity.detached()) return false; + + // Consider + if (!shouldBeRemoved(now, entity)) return false; + + String message = Txt.parse("Player %s with id %s was removed due to inactivity.", entity.getName(), entity.getId()); + + for (CommandSender recipient : recipients) + { + MixinMessage.get().messageOne(recipient, message); + } + + // Apply + entity.detach(); + + return true; + } + + public static boolean shouldBeRemoved(long now, SenderEntity entity) + { + EventMassiveCorePlayercleanToleranceMillis event = new EventMassiveCorePlayercleanToleranceMillis(now, entity); + event.run(); + return event.shouldBeRemoved(); + } + +}