geforkt von Mirrors/FastAsyncWorldEdit
Changes to API and something else I forgot because I got distracted
Dieser Commit ist enthalten in:
Ursprung
475330720e
Commit
99f7b23a8c
@ -27,6 +27,7 @@ import com.boydti.fawe.util.Jars;
|
||||
import com.boydti.fawe.util.TaskManager;
|
||||
import com.boydti.fawe.util.WEManager;
|
||||
import com.boydti.fawe.util.image.ImageViewer;
|
||||
import com.boydti.fawe.util.task.Task;
|
||||
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||
import com.sk89q.worldedit.bukkit.BukkitPlayer;
|
||||
import io.papermc.lib.PaperLib;
|
||||
@ -84,6 +85,12 @@ public class FaweBukkit implements IFawe, Listener {
|
||||
Bukkit.getServer().shutdown();
|
||||
}
|
||||
|
||||
//Vault is Spigot/Paper only so this needs to be done in the Bukkit module
|
||||
setupVault();
|
||||
|
||||
//PlotSquared support is limited to Spigot/Paper as of 02/20/2020
|
||||
TaskManager.IMP.later(this::setupPlotSquared, 0);
|
||||
|
||||
// Registered delayed Event Listeners
|
||||
TaskManager.IMP.task(() -> {
|
||||
// Fix for ProtocolSupport
|
||||
@ -143,8 +150,7 @@ public class FaweBukkit implements IFawe, Listener {
|
||||
|
||||
@Override
|
||||
public void debug(final String message) {
|
||||
ConsoleCommandSender console = Bukkit.getConsoleSender();
|
||||
console.sendMessage(message);
|
||||
Bukkit.getConsoleSender().sendMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -187,8 +193,7 @@ public class FaweBukkit implements IFawe, Listener {
|
||||
/**
|
||||
* Vault isn't required, but used for setting player permissions (WorldEdit bypass)
|
||||
*/
|
||||
@Override
|
||||
public void setupVault() {
|
||||
private void setupVault() {
|
||||
try {
|
||||
this.vault = new VaultUtil();
|
||||
} catch (final Throwable ignored) {
|
||||
@ -298,7 +303,7 @@ public class FaweBukkit implements IFawe, Listener {
|
||||
|
||||
@Override
|
||||
public String getPlatform() {
|
||||
return "bukkit";
|
||||
return "Bukkit";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -319,8 +324,7 @@ public class FaweBukkit implements IFawe, Listener {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setupPlotSquared() {
|
||||
private void setupPlotSquared() {
|
||||
WEManager.IMP.managers.add(new com.boydti.fawe.bukkit.regions.plotsquared.PlotSquaredFeature());
|
||||
log.debug("Plugin 'PlotSquared' found. Using it now.");
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ import com.github.luben.zstd.util.Native;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.platform.Actor;
|
||||
import com.sk89q.worldedit.session.request.Request;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TextComponent;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -152,6 +154,20 @@ public class Fawe {
|
||||
debugPlain((String) s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write something to the console
|
||||
*
|
||||
* @param c The Component to be printed
|
||||
*/
|
||||
public static void debug(Component c) {
|
||||
Actor actor = Request.request().getActor();
|
||||
if (actor != null && actor.isPlayer()) {
|
||||
actor.printDebug(c);
|
||||
return;
|
||||
}
|
||||
debugPlain(c.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* The platform specific implementation
|
||||
*/
|
||||
@ -178,7 +194,6 @@ public class Fawe {
|
||||
*/
|
||||
this.setupMemoryListener();
|
||||
this.timer = new FaweTimer();
|
||||
Fawe.this.IMP.setupVault();
|
||||
|
||||
// Delayed worldedit setup
|
||||
TaskManager.IMP.later(() -> {
|
||||
@ -187,7 +202,6 @@ public class Fawe {
|
||||
// transformParser = new DefaultTransformParser(getWorldEdit());
|
||||
visualQueue = new VisualQueue(3);
|
||||
WEManager.IMP.managers.addAll(Fawe.this.IMP.getMaskManagers());
|
||||
IMP.setupPlotSquared();
|
||||
} catch (Throwable ignored) {}
|
||||
}, 0);
|
||||
|
||||
@ -303,7 +317,6 @@ public class Fawe {
|
||||
} catch (Throwable e) {
|
||||
debug("====== Failed to load config ======");
|
||||
debug("Please validate your yaml files:");
|
||||
debug("====================================");
|
||||
e.printStackTrace();
|
||||
debug("====================================");
|
||||
}
|
||||
@ -328,8 +341,7 @@ public class Fawe {
|
||||
Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL);
|
||||
Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL);
|
||||
debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======");
|
||||
debug(e);
|
||||
debug("===============================================");
|
||||
debug(e.getMessage());
|
||||
debug("FAWE will work but won't compress data as much");
|
||||
debug("===============================================");
|
||||
}
|
||||
@ -339,15 +351,13 @@ public class Fawe {
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
|
||||
debug(e);
|
||||
debug("===============================================");
|
||||
debug(e.getMessage());
|
||||
debug("FAWE will work but compression will be slower");
|
||||
debug(" - Try updating your JVM / OS");
|
||||
debug(" - Report this issue if you cannot resolve it");
|
||||
debug("===============================================");
|
||||
}
|
||||
}
|
||||
try {
|
||||
String arch = System.getenv("PROCESSOR_ARCHITECTURE");
|
||||
String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432");
|
||||
boolean x86OS = !(arch.endsWith("64") || wow64Arch != null && wow64Arch.endsWith("64"));
|
||||
@ -357,7 +367,6 @@ public class Fawe {
|
||||
debug("You are running 32-bit Java on a 64-bit machine");
|
||||
debug("====================================");
|
||||
}
|
||||
} catch (Throwable ignore) {}
|
||||
}
|
||||
|
||||
private void setupMemoryListener() {
|
||||
@ -391,7 +400,6 @@ public class Fawe {
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
debug("====== MEMORY LISTENER ERROR ======");
|
||||
debug("===================================");
|
||||
debug("FAWE needs access to the JVM memory system:");
|
||||
debug(" - Change your Java security settings");
|
||||
debug(" - Disable this with `max-memory-percent: -1`");
|
||||
|
@ -19,8 +19,6 @@ public interface IFawe {
|
||||
|
||||
Player wrap(final Object obj);
|
||||
|
||||
void setupVault();
|
||||
|
||||
TaskManager getTaskManager();
|
||||
|
||||
Collection<FaweMaskManager> getMaskManagers();
|
||||
@ -45,5 +43,4 @@ public interface IFawe {
|
||||
|
||||
Preloader getPreloader();
|
||||
|
||||
void setupPlotSquared();
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ import java.util.UUID;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A clipboard with disk backed storage. (lower memory + loads on crash)
|
||||
* - Uses an auto closable RandomAccessFile for getting / setting id / data
|
||||
* - I don't know how to reduce nbt / entities to O(2) complexity, so it is stored in memory.
|
||||
* A clipboard with disk backed storage. (lower memory + loads on crash) - Uses an auto closable
|
||||
* RandomAccessFile for getting / setting id / data - I don't know how to reduce nbt / entities to
|
||||
* O(2) complexity, so it is stored in memory.
|
||||
*/
|
||||
public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
|
||||
@ -57,18 +57,27 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
|
||||
private FileChannel fileChannel;
|
||||
private boolean hasBiomes;
|
||||
private int ylast;
|
||||
private int ylasti;
|
||||
private int zlast;
|
||||
private int zlasti;
|
||||
|
||||
public DiskOptimizedClipboard(BlockVector3 dimensions, UUID uuid) {
|
||||
this(dimensions, MainUtil.getFile(Fawe.get() != null ? Fawe.imp().getDirectory() : new File("."), Settings.IMP.PATHS.CLIPBOARD + File.separator + uuid + ".bd"));
|
||||
this(dimensions, MainUtil
|
||||
.getFile(Fawe.get() != null ? Fawe.imp().getDirectory() : new File("."),
|
||||
Settings.IMP.PATHS.CLIPBOARD + File.separator + uuid + ".bd"));
|
||||
}
|
||||
|
||||
public DiskOptimizedClipboard(BlockVector3 dimensions) {
|
||||
this(dimensions, MainUtil.getFile(Fawe.imp() != null ? Fawe.imp().getDirectory() : new File("."), Settings.IMP.PATHS.CLIPBOARD + File.separator + UUID.randomUUID() + ".bd"));
|
||||
this(dimensions, MainUtil
|
||||
.getFile(Fawe.imp() != null ? Fawe.imp().getDirectory() : new File("."),
|
||||
Settings.IMP.PATHS.CLIPBOARD + File.separator + UUID.randomUUID() + ".bd"));
|
||||
}
|
||||
|
||||
public DiskOptimizedClipboard(BlockVector3 dimensions, File file) {
|
||||
super(dimensions);
|
||||
if (getWidth() > Character.MAX_VALUE || getHeight() > Character.MAX_VALUE || getLength() > Character.MAX_VALUE) {
|
||||
if (getWidth() > Character.MAX_VALUE || getHeight() > Character.MAX_VALUE
|
||||
|| getLength() > Character.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Too large");
|
||||
}
|
||||
nbtMap = new HashMap<>();
|
||||
@ -99,21 +108,6 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return file.toURI();
|
||||
}
|
||||
|
||||
private static BlockVector3 readSize(File file) {
|
||||
try (DataInputStream is = new DataInputStream(new FileInputStream(file))) {
|
||||
is.skipBytes(2);
|
||||
return BlockVector3.at(is.readChar(), is.readChar(), is.readChar());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public DiskOptimizedClipboard(File file) {
|
||||
super(readSize(file));
|
||||
nbtMap = new HashMap<>();
|
||||
@ -130,6 +124,21 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockVector3 readSize(File file) {
|
||||
try (DataInputStream is = new DataInputStream(new FileInputStream(file))) {
|
||||
is.skipBytes(2);
|
||||
return BlockVector3.at(is.readChar(), is.readChar(), is.readChar());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return file.toURI();
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
@ -186,7 +195,9 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
|
||||
@Override
|
||||
public void streamBiomes(IntValueReader task) {
|
||||
if (!hasBiomes()) return;
|
||||
if (!hasBiomes()) {
|
||||
return;
|
||||
}
|
||||
int index = 0;
|
||||
int mbbIndex = HEADER_SIZE + (getVolume() << 1);
|
||||
try {
|
||||
@ -209,7 +220,8 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
|
||||
public BlockArrayClipboard toClipboard() {
|
||||
try {
|
||||
CuboidRegion region = new CuboidRegion(BlockVector3.at(0, 0, 0), BlockVector3.at(getWidth() - 1, getHeight() - 1, getLength() - 1));
|
||||
CuboidRegion region = new CuboidRegion(BlockVector3.at(0, 0, 0),
|
||||
BlockVector3.at(getWidth() - 1, getHeight() - 1, getLength() - 1));
|
||||
int ox = byteBuffer.getShort(8);
|
||||
int oy = byteBuffer.getShort(10);
|
||||
int oz = byteBuffer.getShort(12);
|
||||
@ -240,7 +252,9 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
|
||||
private void closeDirectBuffer(ByteBuffer cb) {
|
||||
if (cb == null || !cb.isDirect()) return;
|
||||
if (cb == null || !cb.isDirect()) {
|
||||
return;
|
||||
}
|
||||
// we could use this type cast and call functions without reflection code,
|
||||
// but static import from sun.* package is risky for non-SUN virtual machine.
|
||||
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
||||
@ -256,7 +270,8 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
final Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
|
||||
theUnsafeField.setAccessible(true);
|
||||
final Object theUnsafe = theUnsafeField.get(null);
|
||||
final Method invokeCleanerMethod = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class);
|
||||
final Method invokeCleanerMethod = unsafeClass
|
||||
.getMethod("invokeCleaner", ByteBuffer.class);
|
||||
invokeCleanerMethod.invoke(theUnsafe, cb);
|
||||
} catch (Exception e) {
|
||||
System.gc();
|
||||
@ -283,11 +298,6 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
private int ylast;
|
||||
private int ylasti;
|
||||
private int zlast;
|
||||
private int zlasti;
|
||||
|
||||
@Override
|
||||
public Collection<CompoundTag> getTileEntities() {
|
||||
return nbtMap.values();
|
||||
@ -410,7 +420,8 @@ public class DiskOptimizedClipboard extends LinearClipboard {
|
||||
@Nullable
|
||||
@Override
|
||||
public Entity createEntity(Location location, BaseEntity entity) {
|
||||
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location, entity);
|
||||
BlockArrayClipboard.ClipboardEntity ret = new BlockArrayClipboard.ClipboardEntity(location,
|
||||
entity);
|
||||
entities.add(ret);
|
||||
return ret;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import static java.lang.System.arraycopy;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
import com.sk89q.worldedit.util.formatting.WorldEditText;
|
||||
import com.sk89q.worldedit.util.formatting.text.Component;
|
||||
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
|
||||
import com.boydti.fawe.config.Settings;
|
||||
@ -30,7 +29,6 @@ import com.sk89q.worldedit.history.changeset.ChangeSet;
|
||||
import com.sk89q.worldedit.util.Location;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@ -62,7 +60,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.UUID;
|
||||
@ -727,7 +724,7 @@ public class MainUtil {
|
||||
public static Object copyNd(Object arr) {
|
||||
if (arr.getClass().isArray()) {
|
||||
int innerArrayLength = Array.getLength(arr);
|
||||
Class component = arr.getClass().getComponentType();
|
||||
Class<?> component = arr.getClass().getComponentType();
|
||||
Object newInnerArray = Array.newInstance(component, innerArrayLength);
|
||||
if (component.isPrimitive()) {
|
||||
arraycopy(arr, 0, newInnerArray, 0, innerArrayLength);
|
||||
@ -740,7 +737,7 @@ public class MainUtil {
|
||||
}
|
||||
return newInnerArray;
|
||||
} else {
|
||||
return arr;//cant deep copy an opac object??
|
||||
return arr;//can't deep copy an opac object??
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,17 +12,18 @@ package net.jpountz.util;
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This file has been modified for use in the FAWE project.
|
||||
*/
|
||||
|
||||
import com.boydti.fawe.Fawe;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
/**
|
||||
* FOR INTERNAL USE ONLY
|
||||
*/
|
||||
/** FOR INTERNAL USE ONLY */
|
||||
public enum Native {
|
||||
;
|
||||
|
||||
@ -31,7 +32,7 @@ public enum Native {
|
||||
WINDOWS("win32", "so"), LINUX("linux", "so"), MAC("darwin", "dylib"), SOLARIS("solaris", "so");
|
||||
public final String name, libExtension;
|
||||
|
||||
OS(String name, String libExtension) {
|
||||
private OS(String name, String libExtension) {
|
||||
this.name = name;
|
||||
this.libExtension = libExtension;
|
||||
}
|
||||
@ -59,7 +60,9 @@ public enum Native {
|
||||
|
||||
private static String resourceName() {
|
||||
OS os = os();
|
||||
return "/" + os.name + "/" + arch() + "/liblz4-java." + os.libExtension;
|
||||
String packagePrefix = Native.class.getPackage().getName().replace('.', '/');
|
||||
|
||||
return "/" + packagePrefix + "/" + os.name + "/" + arch() + "/liblz4-java." + os.libExtension;
|
||||
}
|
||||
|
||||
private static boolean loaded = false;
|
||||
@ -68,21 +71,57 @@ public enum Native {
|
||||
return loaded;
|
||||
}
|
||||
|
||||
private static void cleanupOldTempLibs() {
|
||||
String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
|
||||
File dir = new File(tempFolder);
|
||||
|
||||
File[] tempLibFiles = dir.listFiles((dir1, name) ->
|
||||
name.startsWith("liblz4-java-") && !name.endsWith(".lck"));
|
||||
if(tempLibFiles != null) {
|
||||
for(File tempLibFile : tempLibFiles) {
|
||||
File lckFile = new File(tempLibFile.getAbsolutePath() + ".lck");
|
||||
if(!lckFile.exists()) {
|
||||
try {
|
||||
tempLibFile.delete();
|
||||
}
|
||||
catch(SecurityException e) {
|
||||
System.err.println("Failed to delete old temp lib" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void load() {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanupOldTempLibs();
|
||||
|
||||
// Try to load lz4-java (liblz4-java.so on Linux) from the java.library.path.
|
||||
try {
|
||||
System.loadLibrary("lz4-java");
|
||||
loaded = true;
|
||||
return;
|
||||
} catch (UnsatisfiedLinkError ex) {
|
||||
// Doesn't exist, so proceed to loading bundled library.
|
||||
}
|
||||
|
||||
String resourceName = resourceName();
|
||||
InputStream is = Fawe.class.getResourceAsStream(resourceName);
|
||||
InputStream is = Native.class.getResourceAsStream(resourceName);
|
||||
if (is == null) {
|
||||
throw new UnsupportedOperationException("Unsupported OS/arch, cannot find " + resourceName + ". Please try building from source.");
|
||||
}
|
||||
File tempLib;
|
||||
File tempLib = null;
|
||||
File tempLibLock = null;
|
||||
try {
|
||||
tempLib = File.createTempFile("liblz4-java", "." + os().libExtension);
|
||||
// Create the .lck file first to avoid a race condition
|
||||
// with other concurrently running Java processes using lz4-java.
|
||||
tempLibLock = File.createTempFile("liblz4-java-", "." + os().libExtension + ".lck");
|
||||
tempLib = new File(tempLibLock.getAbsolutePath().replaceFirst(".lck$", ""));
|
||||
// copy to tempLib
|
||||
FileOutputStream out = new FileOutputStream(tempLib);
|
||||
try {
|
||||
try (FileOutputStream out = new FileOutputStream(tempLib)) {
|
||||
byte[] buf = new byte[4096];
|
||||
while (true) {
|
||||
int read = is.read(buf);
|
||||
@ -91,34 +130,27 @@ public enum Native {
|
||||
}
|
||||
out.write(buf, 0, read);
|
||||
}
|
||||
try {
|
||||
out.close();
|
||||
out = null;
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
System.load(tempLib.getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IOException e) {
|
||||
throw new ExceptionInInitializerError("Cannot unpack liblz4-java: " + e);
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
if (tempLib != null && tempLib.exists()) {
|
||||
if (!loaded) {
|
||||
tempLib.delete();
|
||||
if (tempLib != null && tempLib.exists()) {
|
||||
if (!tempLib.delete()) {
|
||||
throw new ExceptionInInitializerError("Cannot unpack liblz4-java / cannot delete a temporary native library " + tempLib);
|
||||
}
|
||||
}
|
||||
if (tempLibLock != null && tempLibLock.exists()) {
|
||||
if (!tempLibLock.delete()) {
|
||||
throw new ExceptionInInitializerError("Cannot unpack liblz4-java / cannot delete a temporary lock file " + tempLibLock);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// try to delete on exit, does it work on Windows?
|
||||
tempLib.deleteOnExit();
|
||||
tempLibLock.deleteOnExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ExceptionInInitializerError("Cannot unpack liblz4-java");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren