Refactoring in Utils class
This commit is contained in:
parent
160df31497
commit
416972ca7b
@ -1,87 +1,102 @@
|
||||
package com.rarchives.ripme.utils;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineEvent;
|
||||
|
||||
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||
import org.apache.commons.configuration.ConfigurationException;
|
||||
import org.apache.commons.configuration.PropertiesConfiguration;
|
||||
import org.apache.log4j.LogManager;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
|
||||
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.Line;
|
||||
import javax.sound.sampled.LineEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
/**
|
||||
* Common utility functions used in various places throughout the project.
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
private static final String RIP_DIRECTORY = "rips";
|
||||
private static final String configFile = "rip.properties";
|
||||
private static final String CONFIG_FILE = "rip.properties";
|
||||
private static final String OS = System.getProperty("os.name").toLowerCase();
|
||||
private static final Logger logger = Logger.getLogger(Utils.class);
|
||||
private static final Logger LOGGER = Logger.getLogger(Utils.class);
|
||||
private static final int SHORTENED_PATH_LENGTH = 12;
|
||||
|
||||
private static PropertiesConfiguration config;
|
||||
private static HashMap<String, HashMap<String, String>> cookieCache;
|
||||
|
||||
static {
|
||||
cookieCache = new HashMap<>();
|
||||
|
||||
try {
|
||||
String configPath = getConfigFilePath();
|
||||
File f = new File(configPath);
|
||||
if (!f.exists()) {
|
||||
File file = new File(configPath);
|
||||
|
||||
if (!file.exists()) {
|
||||
// Use default bundled with .jar
|
||||
configPath = configFile;
|
||||
configPath = CONFIG_FILE;
|
||||
}
|
||||
|
||||
config = new PropertiesConfiguration(configPath);
|
||||
logger.info("Loaded " + config.getPath());
|
||||
if (f.exists()) {
|
||||
LOGGER.info("Loaded " + config.getPath());
|
||||
|
||||
if (file.exists()) {
|
||||
// Config was loaded from file
|
||||
if ( !config.containsKey("twitter.auth")
|
||||
|| !config.containsKey("twitter.max_requests")
|
||||
|| !config.containsKey("tumblr.auth")
|
||||
|| !config.containsKey("error.skip404")
|
||||
|| !config.containsKey("gw.api")
|
||||
|| !config.containsKey("page.timeout")
|
||||
|| !config.containsKey("download.max_size")
|
||||
) {
|
||||
if (!config.containsKey("twitter.auth") || !config.containsKey("twitter.max_requests")
|
||||
|| !config.containsKey("tumblr.auth") || !config.containsKey("error.skip404")
|
||||
|| !config.containsKey("gw.api") || !config.containsKey("page.timeout")
|
||||
|| !config.containsKey("download.max_size")) {
|
||||
// Config is missing key fields
|
||||
// Need to reload the default config
|
||||
// See https://github.com/4pr0n/ripme/issues/158
|
||||
logger.warn("Config does not contain key fields, deleting old config");
|
||||
f.delete();
|
||||
config = new PropertiesConfiguration(configFile);
|
||||
logger.info("Loaded " + config.getPath());
|
||||
LOGGER.warn("Config does not contain key fields, deleting old config");
|
||||
file.delete();
|
||||
config = new PropertiesConfiguration(CONFIG_FILE);
|
||||
LOGGER.info("Loaded " + config.getPath());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("[!] Failed to load properties file from " + configFile, e);
|
||||
LOGGER.error("[!] Failed to load properties file from " + CONFIG_FILE, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root rips directory.
|
||||
* @return
|
||||
* Root directory to save rips to.
|
||||
* @throws IOException
|
||||
*
|
||||
* @return Root directory to save rips to.
|
||||
*/
|
||||
public static File getWorkingDirectory() {
|
||||
String currentDir = ".";
|
||||
try {
|
||||
currentDir = new File(".").getCanonicalPath() + File.separator + RIP_DIRECTORY + File.separator;
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while finding working dir: ", e);
|
||||
LOGGER.error("Error while finding working dir: ", e);
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
currentDir = getConfigString("rips.directory", currentDir);
|
||||
}
|
||||
|
||||
File workingDir = new File(currentDir);
|
||||
if (!workingDir.exists()) {
|
||||
workingDir.mkdirs();
|
||||
@ -100,20 +115,19 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static String[] getConfigStringArray(String key) {
|
||||
String[] s = config.getStringArray(key);
|
||||
if (s.length == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
String[] configStringArray = config.getStringArray(key);
|
||||
|
||||
return configStringArray.length == 0 ? null : configStringArray;
|
||||
}
|
||||
|
||||
public static int getConfigInteger(String key, int defaultValue) {
|
||||
return config.getInt(key, defaultValue);
|
||||
}
|
||||
|
||||
public static boolean getConfigBoolean(String key, boolean defaultValue) {
|
||||
return config.getBoolean(key, defaultValue);
|
||||
}
|
||||
|
||||
public static List<String> getConfigList(String key) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (Object obj : config.getList(key, new ArrayList<String>())) {
|
||||
@ -123,13 +137,24 @@ public class Utils {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static void setConfigBoolean(String key, boolean value) { config.setProperty(key, value); }
|
||||
public static void setConfigString(String key, String value) { config.setProperty(key, value); }
|
||||
public static void setConfigInteger(String key, int value) { config.setProperty(key, value); }
|
||||
|
||||
public static void setConfigBoolean(String key, boolean value) {
|
||||
config.setProperty(key, value);
|
||||
}
|
||||
|
||||
public static void setConfigString(String key, String value) {
|
||||
config.setProperty(key, value);
|
||||
}
|
||||
|
||||
public static void setConfigInteger(String key, int value) {
|
||||
config.setProperty(key, value);
|
||||
}
|
||||
|
||||
public static void setConfigList(String key, List<Object> list) {
|
||||
config.clearProperty(key);
|
||||
config.addProperty(key, list);
|
||||
}
|
||||
|
||||
public static void setConfigList(String key, Enumeration<Object> enumeration) {
|
||||
config.clearProperty(key);
|
||||
List<Object> list = new ArrayList<>();
|
||||
@ -142,9 +167,9 @@ public class Utils {
|
||||
public static void saveConfig() {
|
||||
try {
|
||||
config.save(getConfigFilePath());
|
||||
logger.info("Saved configuration to " + getConfigFilePath());
|
||||
LOGGER.info("Saved configuration to " + getConfigFilePath());
|
||||
} catch (ConfigurationException e) {
|
||||
logger.error("Error while saving configuration: ", e);
|
||||
LOGGER.error("Error while saving configuration: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +201,6 @@ public class Utils {
|
||||
return System.getenv("LOCALAPPDATA") + File.separator + "ripme";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the directory of where the config file is stored on a UNIX machine.
|
||||
*/
|
||||
@ -197,13 +221,14 @@ public class Utils {
|
||||
*/
|
||||
private static boolean portableMode() {
|
||||
try {
|
||||
File f = new File(new File(".").getCanonicalPath() + File.separator + configFile);
|
||||
if(f.exists() && !f.isDirectory()) {
|
||||
File file = new File(new File(".").getCanonicalPath() + File.separator + CONFIG_FILE);
|
||||
if (file.exists() && !file.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -229,6 +254,7 @@ public class Utils {
|
||||
return ".";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the url history file
|
||||
*/
|
||||
@ -248,26 +274,23 @@ public class Utils {
|
||||
* Gets the path to the configuration file.
|
||||
*/
|
||||
private static String getConfigFilePath() {
|
||||
return getConfigDir() + File.separator + configFile;
|
||||
return getConfigDir() + File.separator + CONFIG_FILE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current working directory (CWD) from a File.
|
||||
* @param saveAs
|
||||
* The File path
|
||||
* @return
|
||||
* saveAs in relation to the CWD
|
||||
*
|
||||
* @param saveAs The File path
|
||||
* @return saveAs in relation to the CWD
|
||||
*/
|
||||
public static String removeCWD(File saveAs) {
|
||||
String prettySaveAs = saveAs.toString();
|
||||
try {
|
||||
prettySaveAs = saveAs.getCanonicalPath();
|
||||
String cwd = new File(".").getCanonicalPath() + File.separator;
|
||||
prettySaveAs = prettySaveAs.replace(
|
||||
cwd,
|
||||
"." + File.separator);
|
||||
prettySaveAs = prettySaveAs.replace(cwd, "." + File.separator);
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception: ", e);
|
||||
LOGGER.error("Exception: ", e);
|
||||
}
|
||||
return prettySaveAs;
|
||||
}
|
||||
@ -278,7 +301,6 @@ public class Utils {
|
||||
*
|
||||
* @param url The URL to filter/strip
|
||||
* @param parameter The parameter to strip
|
||||
*
|
||||
* @return The stripped URL
|
||||
*/
|
||||
public static String stripURLParameter(String url, String parameter) {
|
||||
@ -290,7 +312,7 @@ public class Utils {
|
||||
}
|
||||
|
||||
if (paramIndex > 0) {
|
||||
int nextParam = url.indexOf("&", paramIndex+1);
|
||||
int nextParam = url.indexOf('&', paramIndex + 1);
|
||||
if (nextParam != -1) {
|
||||
String c = "&";
|
||||
if (wasFirstParam) {
|
||||
@ -307,10 +329,9 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Removes the current working directory from a given filename
|
||||
* @param file
|
||||
* Path to the file
|
||||
* @return
|
||||
* 'file' without the leading current working directory
|
||||
*
|
||||
* @param file Path to the file
|
||||
* @return 'file' without the leading current working directory
|
||||
*/
|
||||
public static String removeCWD(String file) {
|
||||
return removeCWD(new File(file));
|
||||
@ -320,12 +341,11 @@ public class Utils {
|
||||
* Get a list of all Classes within a package.
|
||||
* Works with file system projects and jar files!
|
||||
* Borrowed from StackOverflow, but I don't have a link :[
|
||||
* @param pkgname
|
||||
* The name of the package
|
||||
* @return
|
||||
* List of classes within the package
|
||||
*
|
||||
* @param pkgname The name of the package
|
||||
* @return List of classes within the package
|
||||
*/
|
||||
public static ArrayList<Class<?>> getClassesForPackage(String pkgname) {
|
||||
public static List<Class<?>> getClassesForPackage(String pkgname) {
|
||||
ArrayList<Class<?>> classes = new ArrayList<>();
|
||||
String relPath = pkgname.replace('.', '/');
|
||||
URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
|
||||
@ -334,7 +354,8 @@ public class Utils {
|
||||
}
|
||||
|
||||
String fullPath = resource.getFile();
|
||||
File directory = null;
|
||||
File directory;
|
||||
|
||||
try {
|
||||
directory = new File(resource.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
@ -356,8 +377,7 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Load from JAR
|
||||
try {
|
||||
String jarPath = fullPath
|
||||
@ -376,7 +396,7 @@ public class Utils {
|
||||
try {
|
||||
classes.add(Class.forName(className));
|
||||
} catch (ClassNotFoundException e) {
|
||||
logger.error("ClassNotFoundException loading " + className);
|
||||
LOGGER.error("ClassNotFoundException loading " + className);
|
||||
jarFile.close(); // Resource leak fix?
|
||||
throw new RuntimeException("ClassNotFoundException loading " + className);
|
||||
}
|
||||
@ -384,20 +404,18 @@ public class Utils {
|
||||
}
|
||||
jarFile.close(); // Eclipse said not closing it would have a resource leak
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while loading jar file:", e);
|
||||
LOGGER.error("Error while loading jar file:", e);
|
||||
throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e);
|
||||
}
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
private static final int SHORTENED_PATH_LENGTH = 12;
|
||||
/**
|
||||
* Shortens the path to a file
|
||||
* @param path
|
||||
* String of the path to the file
|
||||
* @return
|
||||
* The simplified path to the file.
|
||||
*
|
||||
* @param path String of the path to the file
|
||||
* @return The simplified path to the file.
|
||||
*/
|
||||
public static String shortenPath(String path) {
|
||||
return shortenPath(new File(path));
|
||||
@ -405,10 +423,9 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Shortens the path to a file
|
||||
* @param file
|
||||
* File object that you want the shortened path of.
|
||||
* @return
|
||||
* The simplified path to the file.
|
||||
*
|
||||
* @param file File object that you want the shortened path of.
|
||||
* @return The simplified path to the file.
|
||||
*/
|
||||
public static String shortenPath(File file) {
|
||||
String path = removeCWD(file);
|
||||
@ -422,10 +439,9 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Sanitizes a string so that a filesystem can handle it
|
||||
* @param text
|
||||
* The text to be sanitized.
|
||||
* @return
|
||||
* The sanitized text.
|
||||
*
|
||||
* @param text The text to be sanitized.
|
||||
* @return The sanitized text.
|
||||
*/
|
||||
public static String filesystemSanitized(String text) {
|
||||
text = text.replaceAll("[^a-zA-Z0-9.-]", "_");
|
||||
@ -463,13 +479,13 @@ public class Utils {
|
||||
|
||||
// Get a List of all Directories and check its lowercase
|
||||
// if file exists return it
|
||||
File f = new File(path.substring(0, index));
|
||||
ArrayList<String> names = new ArrayList<String>(Arrays.asList(f.list()));
|
||||
File file = new File(path.substring(0, index));
|
||||
ArrayList<String> names = new ArrayList<>(Arrays.asList(file.list()));
|
||||
|
||||
for (String s : names) {
|
||||
if(s.toLowerCase().equals(lastPart)) {
|
||||
for (String name : names) {
|
||||
if (name.toLowerCase().equals(lastPart)) {
|
||||
// Building Path of existing file
|
||||
return path.substring(0, index) + File.separator + s;
|
||||
return path.substring(0, index) + File.separator + name;
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,10 +494,9 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Converts an integer into a human readable string
|
||||
* @param bytes
|
||||
* Non-human readable integer.
|
||||
* @return
|
||||
* Human readable interpretation of a byte.
|
||||
*
|
||||
* @param bytes Non-human readable integer.
|
||||
* @return Human readable interpretation of a byte.
|
||||
*/
|
||||
public static String bytesToHumanReadable(int bytes) {
|
||||
float fbytes = (float) bytes;
|
||||
@ -496,6 +511,7 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Gets and returns a list of all the album rippers present in the "com.rarchives.ripme.ripper.rippers" package.
|
||||
*
|
||||
* @return List<String> of all album rippers present.
|
||||
*/
|
||||
public static List<String> getListOfAlbumRippers() throws Exception {
|
||||
@ -508,6 +524,7 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Gets and returns a list of all video rippers present in the "com.rarchives.rime.rippers.video" package
|
||||
*
|
||||
* @return List<String> of all the video rippers.
|
||||
*/
|
||||
public static List<String> getListOfVideoRippers() throws Exception {
|
||||
@ -520,8 +537,8 @@ public class Utils {
|
||||
|
||||
/**
|
||||
* Plays a sound from a file.
|
||||
* @param filename
|
||||
* Path to the sound file
|
||||
*
|
||||
* @param filename Path to the sound file
|
||||
*/
|
||||
public static void playSound(String filename) {
|
||||
URL resource = ClassLoader.getSystemClassLoader().getResource(filename);
|
||||
@ -535,7 +552,7 @@ public class Utils {
|
||||
clip.open(AudioSystem.getAudioInputStream(resource));
|
||||
clip.start();
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to play sound " + filename, e);
|
||||
LOGGER.error("Failed to play sound " + filename, e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -544,27 +561,25 @@ public class Utils {
|
||||
*/
|
||||
public static void configureLogger() {
|
||||
LogManager.shutdown();
|
||||
String logFile;
|
||||
if (getConfigBoolean("log.save", false)) {
|
||||
logFile = "log4j.file.properties";
|
||||
}
|
||||
else {
|
||||
logFile = "log4j.properties";
|
||||
}
|
||||
InputStream stream = Utils.class.getClassLoader().getResourceAsStream(logFile);
|
||||
String logFile = getConfigBoolean("log.save", false) ? "log4j.file.properties" : "log4j.properties";
|
||||
|
||||
try (InputStream stream = Utils.class.getClassLoader().getResourceAsStream(logFile)) {
|
||||
if (stream == null) {
|
||||
PropertyConfigurator.configure("src/main/resources/" + logFile);
|
||||
} else {
|
||||
PropertyConfigurator.configure(stream);
|
||||
}
|
||||
logger.info("Loaded " + logFile);
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) { }
|
||||
|
||||
LOGGER.info("Loaded " + logFile);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of strings between two strings.
|
||||
*
|
||||
* @param fullText Text to retrieve from.
|
||||
* @param start String that precedes the desired text
|
||||
* @param finish String that follows the desired text
|
||||
@ -572,25 +587,25 @@ public class Utils {
|
||||
*/
|
||||
public static List<String> between(String fullText, String start, String finish) {
|
||||
List<String> result = new ArrayList<>();
|
||||
int i, j;
|
||||
i = fullText.indexOf(start);
|
||||
int i = fullText.indexOf(start);
|
||||
|
||||
while (i >= 0) {
|
||||
i += start.length();
|
||||
j = fullText.indexOf(finish, i);
|
||||
int j = fullText.indexOf(finish, i);
|
||||
if (j < 0) {
|
||||
break;
|
||||
}
|
||||
result.add(fullText.substring(i, j));
|
||||
i = fullText.indexOf(start, j + finish.length());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an URL query
|
||||
*
|
||||
* @param query
|
||||
* The query part of an URL
|
||||
* @param query The query part of an URL
|
||||
* @return The map of all query parameters
|
||||
*/
|
||||
public static Map<String, String> parseUrlQuery(String query) {
|
||||
@ -622,10 +637,8 @@ public class Utils {
|
||||
/**
|
||||
* Parses an URL query and returns the requested parameter's value
|
||||
*
|
||||
* @param query
|
||||
* The query part of an URL
|
||||
* @param key
|
||||
* The key whose value is requested
|
||||
* @param query The query part of an URL
|
||||
* @param key The key whose value is requested
|
||||
* @return The associated value or null if key wasn't found
|
||||
*/
|
||||
public static String parseUrlQuery(String query, String key) {
|
||||
@ -655,18 +668,13 @@ public class Utils {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HashMap<String, HashMap<String, String>> cookieCache;
|
||||
static {
|
||||
cookieCache = new HashMap<String, HashMap<String, String>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the cookies from a certain host
|
||||
*/
|
||||
public static Map<String, String> getCookies(String host) {
|
||||
HashMap<String, String> domainCookies = cookieCache.get(host);
|
||||
if (domainCookies == null) {
|
||||
domainCookies = new HashMap<String, String>();
|
||||
domainCookies = new HashMap<>();
|
||||
String cookiesConfig = getConfigString("cookies." + host, "");
|
||||
for (String pair : cookiesConfig.split(" ")) {
|
||||
pair = pair.trim();
|
||||
@ -690,20 +698,21 @@ public class Utils {
|
||||
if (langSelect == null) {
|
||||
if (!getConfigString("lang", "").equals("")) {
|
||||
String[] langCode = getConfigString("lang", "").split("_");
|
||||
logger.info("Setting locale to " + getConfigString("lang", ""));
|
||||
LOGGER.info("Setting locale to " + getConfigString("lang", ""));
|
||||
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
||||
}
|
||||
} else {
|
||||
String[] langCode = langSelect.split("_");
|
||||
logger.info("Setting locale to " + langSelect);
|
||||
LOGGER.info("Setting locale to " + langSelect);
|
||||
return ResourceBundle.getBundle("LabelsBundle", new Locale(langCode[0], langCode[1]), new UTF8Control());
|
||||
}
|
||||
try {
|
||||
logger.info("Setting locale to default");
|
||||
LOGGER.info("Setting locale to default");
|
||||
return ResourceBundle.getBundle("LabelsBundle", Locale.getDefault(), new UTF8Control());
|
||||
} catch (MissingResourceException e) {
|
||||
logger.info("Setting locale to root");
|
||||
LOGGER.info("Setting locale to root");
|
||||
return ResourceBundle.getBundle("LabelsBundle", Locale.ROOT);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user