Migrate from Properties to Commons-Configuration
- Added loading of properties file before database initialization Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
This commit is contained in:
@@ -353,6 +353,19 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-configuration2</artifactId>
|
||||||
|
<version>2.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-beanutils</groupId>
|
||||||
|
<artifactId>commons-beanutils</artifactId>
|
||||||
|
<version>1.9.2</version>
|
||||||
|
<!-- commons-configuration2 requires during runtime -->
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
<profiles>
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
package org.libresonic.player.service;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.commons.configuration2.Configuration;
|
||||||
|
import org.apache.commons.configuration2.FileBasedConfiguration;
|
||||||
|
import org.apache.commons.configuration2.ImmutableConfiguration;
|
||||||
|
import org.apache.commons.configuration2.MapConfiguration;
|
||||||
|
import org.apache.commons.configuration2.PropertiesConfiguration;
|
||||||
|
import org.apache.commons.configuration2.PropertiesConfigurationLayout;
|
||||||
|
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
|
||||||
|
import org.apache.commons.configuration2.builder.fluent.Parameters;
|
||||||
|
import org.apache.commons.configuration2.ex.ConfigurationException;
|
||||||
|
import org.apache.commons.configuration2.sync.ReadWriteSynchronizer;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.libresonic.player.Logger;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class ApacheCommonsConfigurationService {
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(ApacheCommonsConfigurationService.class);
|
||||||
|
|
||||||
|
private final FileBasedConfigurationBuilder<FileBasedConfiguration> builder;
|
||||||
|
|
||||||
|
private final Configuration config;
|
||||||
|
|
||||||
|
public static final String HEADER_COMMENT = "Libresonic preferences. NOTE: This file is automatically generated."
|
||||||
|
+ " Do not modify while application is running";
|
||||||
|
|
||||||
|
public ApacheCommonsConfigurationService() {
|
||||||
|
File propertyFile = SettingsService.getPropertyFile();
|
||||||
|
if(!propertyFile.exists()) {
|
||||||
|
try {
|
||||||
|
FileUtils.touch(propertyFile);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Could not create new property file", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parameters params = new Parameters();
|
||||||
|
PropertiesConfigurationLayout layout = new PropertiesConfigurationLayout();
|
||||||
|
// https://issues.apache.org/jira/browse/CONFIGURATION-644
|
||||||
|
// layout.setHeaderComment(HEADER_COMMENT);
|
||||||
|
layout.setGlobalSeparator("=");
|
||||||
|
builder = new FileBasedConfigurationBuilder<FileBasedConfiguration>(PropertiesConfiguration.class).configure(
|
||||||
|
params.properties()
|
||||||
|
.setFile(propertyFile)
|
||||||
|
.setSynchronizer(new ReadWriteSynchronizer())
|
||||||
|
.setLayout(layout));
|
||||||
|
try {
|
||||||
|
config = builder.getConfiguration();
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
throw new RuntimeException("Could not load property file at " + propertyFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try {
|
||||||
|
builder.save();
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
LOG.error("Unable to write to property file.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getProperty(String key) {
|
||||||
|
return config.getProperty(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(String key) {
|
||||||
|
return config.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearProperty(String key) {
|
||||||
|
config.clearProperty(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(String key, String defaultValue) {
|
||||||
|
return config.getString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProperty(String key, Object value) {
|
||||||
|
config.setProperty(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLong(String key, long defaultValue) {
|
||||||
|
return config.getLong(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInteger(String key, int defaultValue) {
|
||||||
|
return config.getInteger(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getBoolean(String key, boolean defaultValue) {
|
||||||
|
return config.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableConfiguration getImmutableSnapshot() {
|
||||||
|
MapConfiguration mapConfiguration = new MapConfiguration(new HashMap<>());
|
||||||
|
mapConfiguration.copy(config);
|
||||||
|
return mapConfiguration;
|
||||||
|
}
|
||||||
|
}
|
||||||
+108
-152
@@ -19,29 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.libresonic.player.service;
|
package org.libresonic.player.service;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.StringTokenizer;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
import org.libresonic.player.Logger;
|
import org.libresonic.player.Logger;
|
||||||
@@ -61,6 +38,12 @@ import org.libresonic.player.util.FileUtil;
|
|||||||
import org.libresonic.player.util.StringUtil;
|
import org.libresonic.player.util.StringUtil;
|
||||||
import org.libresonic.player.util.Util;
|
import org.libresonic.player.util.Util;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides persistent storage of application settings and preferences.
|
* Provides persistent storage of application settings and preferences.
|
||||||
*
|
*
|
||||||
@@ -219,13 +202,13 @@ public class SettingsService {
|
|||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(SettingsService.class);
|
private static final Logger LOG = Logger.getLogger(SettingsService.class);
|
||||||
|
|
||||||
private Properties properties = new Properties();
|
|
||||||
private List<Theme> themes;
|
private List<Theme> themes;
|
||||||
private List<Locale> locales;
|
private List<Locale> locales;
|
||||||
private InternetRadioDao internetRadioDao;
|
private InternetRadioDao internetRadioDao;
|
||||||
private MusicFolderDao musicFolderDao;
|
private MusicFolderDao musicFolderDao;
|
||||||
private UserDao userDao;
|
private UserDao userDao;
|
||||||
private AvatarDao avatarDao;
|
private AvatarDao avatarDao;
|
||||||
|
private ApacheCommonsConfigurationService configurationService;
|
||||||
private VersionService versionService;
|
private VersionService versionService;
|
||||||
|
|
||||||
private String[] cachedCoverArtFileTypesArray;
|
private String[] cachedCoverArtFileTypesArray;
|
||||||
@@ -234,34 +217,33 @@ public class SettingsService {
|
|||||||
private List<MusicFolder> cachedMusicFolders;
|
private List<MusicFolder> cachedMusicFolders;
|
||||||
private final ConcurrentMap<String, List<MusicFolder>> cachedMusicFoldersPerUser = new ConcurrentHashMap<String, List<MusicFolder>>();
|
private final ConcurrentMap<String, List<MusicFolder>> cachedMusicFoldersPerUser = new ConcurrentHashMap<String, List<MusicFolder>>();
|
||||||
|
|
||||||
private static File libresonicHome;
|
|
||||||
|
|
||||||
private static final long LOCAL_IP_LOOKUP_DELAY_SECONDS = 60;
|
|
||||||
private String localIpAddress;
|
private String localIpAddress;
|
||||||
|
|
||||||
public SettingsService() {
|
private void removeObseleteProperties() {
|
||||||
File propertyFile = getPropertyFile();
|
|
||||||
|
|
||||||
if (propertyFile.exists()) {
|
OBSOLETE_KEYS.forEach( oKey -> {
|
||||||
FileInputStream in = null;
|
if(configurationService.containsKey(oKey)) {
|
||||||
try {
|
LOG.info("Removing obsolete property [" + oKey + ']');
|
||||||
in = new FileInputStream(propertyFile);
|
configurationService.clearProperty(oKey);
|
||||||
properties.load(in);
|
|
||||||
} catch (Exception x) {
|
|
||||||
LOG.error("Unable to read from property file.", x);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(in);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Remove obsolete properties.
|
}
|
||||||
for (Iterator<Object> iterator = properties.keySet().iterator(); iterator.hasNext();) {
|
|
||||||
String key = (String) iterator.next();
|
public static synchronized File getLibresonicHome() {
|
||||||
if (OBSOLETE_KEYS.contains(key)) {
|
|
||||||
LOG.info("Removing obsolete property [" + key + ']');
|
File home;
|
||||||
iterator.remove();
|
|
||||||
}
|
String overrideHome = System.getProperty("libresonic.home");
|
||||||
}
|
if (overrideHome != null) {
|
||||||
|
home = new File(overrideHome);
|
||||||
|
} else {
|
||||||
|
boolean isWindows = System.getProperty("os.name", "Windows").toLowerCase().startsWith("windows");
|
||||||
|
home = isWindows ? LIBRESONIC_HOME_WINDOWS : LIBRESONIC_HOME_OTHER;
|
||||||
}
|
}
|
||||||
|
ensureDirectoryPresent(home);
|
||||||
|
|
||||||
|
return home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -284,92 +266,58 @@ public class SettingsService {
|
|||||||
save(true);
|
save(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save(boolean updateChangedDate) {
|
public void save(boolean updateSettingsChanged) {
|
||||||
if (updateChangedDate) {
|
if(updateSettingsChanged) {
|
||||||
setProperty(KEY_SETTINGS_CHANGED, String.valueOf(System.currentTimeMillis()));
|
removeObseleteProperties();
|
||||||
}
|
this.setLong(KEY_SETTINGS_CHANGED, System.currentTimeMillis());
|
||||||
|
|
||||||
OutputStream out = null;
|
|
||||||
try {
|
|
||||||
out = new FileOutputStream(getPropertyFile());
|
|
||||||
properties.store(out, "Libresonic preferences. NOTE: This file is automatically generated.");
|
|
||||||
} catch (Exception x) {
|
|
||||||
LOG.error("Unable to write to property file.", x);
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(out);
|
|
||||||
}
|
}
|
||||||
|
configurationService.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getPropertyFile() {
|
private static void ensureDirectoryPresent(File home) {
|
||||||
return new File(getLibresonicHome(), "libresonic.properties");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the Libresonic home directory.
|
|
||||||
*
|
|
||||||
* @return The Libresonic home directory, if it exists.
|
|
||||||
* @throws RuntimeException If directory doesn't exist.
|
|
||||||
*/
|
|
||||||
public static synchronized File getLibresonicHome() {
|
|
||||||
|
|
||||||
if (libresonicHome != null) {
|
|
||||||
return libresonicHome;
|
|
||||||
}
|
|
||||||
|
|
||||||
File home;
|
|
||||||
|
|
||||||
String overrideHome = System.getProperty("libresonic.home");
|
|
||||||
if (overrideHome != null) {
|
|
||||||
home = new File(overrideHome);
|
|
||||||
} else {
|
|
||||||
boolean isWindows = System.getProperty("os.name", "Windows").toLowerCase().startsWith("windows");
|
|
||||||
home = isWindows ? LIBRESONIC_HOME_WINDOWS : LIBRESONIC_HOME_OTHER;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to create home directory if it doesn't exist.
|
// Attempt to create home directory if it doesn't exist.
|
||||||
if (!home.exists() || !home.isDirectory()) {
|
if (!home.exists() || !home.isDirectory()) {
|
||||||
boolean success = home.mkdirs();
|
boolean success = home.mkdirs();
|
||||||
if (success) {
|
if (!success) {
|
||||||
libresonicHome = home;
|
|
||||||
} else {
|
|
||||||
String message = "The directory " + home + " does not exist. Please create it and make it writable. " +
|
String message = "The directory " + home + " does not exist. Please create it and make it writable. " +
|
||||||
"(You can override the directory location by specifying -Dlibresonic.home=... when " +
|
"(You can override the directory location by specifying -Dlibresonic.home=... when " +
|
||||||
"starting the servlet container.)";
|
"starting the servlet container.)";
|
||||||
System.err.println("ERROR: " + message);
|
throw new RuntimeException(message);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
libresonicHome = home;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return home;
|
public static File getPropertyFile() {
|
||||||
|
File propertyFile = getLibresonicHome();
|
||||||
|
return new File(propertyFile, "libresonic.properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getInt(String key, int defaultValue) {
|
private int getInt(String key, int defaultValue) {
|
||||||
return Integer.valueOf(properties.getProperty(key, String.valueOf(defaultValue)));
|
return configurationService.getInteger(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setInt(String key, int value) {
|
private void setInt(String key, Integer value) {
|
||||||
setProperty(key, String.valueOf(value));
|
setProperty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getLong(String key, long defaultValue) {
|
private long getLong(String key, long defaultValue) {
|
||||||
return Long.valueOf(properties.getProperty(key, String.valueOf(defaultValue)));
|
return configurationService.getLong(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLong(String key, long value) {
|
private void setLong(String key, Long value) {
|
||||||
setProperty(key, String.valueOf(value));
|
setProperty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getBoolean(String key, boolean defaultValue) {
|
private boolean getBoolean(String key, boolean defaultValue) {
|
||||||
return Boolean.valueOf(properties.getProperty(key, String.valueOf(defaultValue)));
|
return configurationService.getBoolean(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBoolean(String key, boolean value) {
|
private void setBoolean(String key, Boolean value) {
|
||||||
setProperty(key, String.valueOf(value));
|
setProperty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getString(String key, String defaultValue) {
|
private String getString(String key, String defaultValue) {
|
||||||
return properties.getProperty(key, defaultValue);
|
return getProperty(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setString(String key, String value) {
|
private void setString(String key, String value) {
|
||||||
@@ -377,7 +325,11 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getIndexString() {
|
public String getIndexString() {
|
||||||
return properties.getProperty(KEY_INDEX_STRING, DEFAULT_INDEX_STRING);
|
return getProperty(KEY_INDEX_STRING, DEFAULT_INDEX_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getProperty(String key, String defaultValue) {
|
||||||
|
return configurationService.getString(key, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIndexString(String indexString) {
|
public void setIndexString(String indexString) {
|
||||||
@@ -385,7 +337,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getIgnoredArticles() {
|
public String getIgnoredArticles() {
|
||||||
return properties.getProperty(KEY_IGNORED_ARTICLES, DEFAULT_IGNORED_ARTICLES);
|
return getProperty(KEY_IGNORED_ARTICLES, DEFAULT_IGNORED_ARTICLES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getIgnoredArticlesAsArray() {
|
public String[] getIgnoredArticlesAsArray() {
|
||||||
@@ -397,7 +349,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getShortcuts() {
|
public String getShortcuts() {
|
||||||
return properties.getProperty(KEY_SHORTCUTS, DEFAULT_SHORTCUTS);
|
return getProperty(KEY_SHORTCUTS, DEFAULT_SHORTCUTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getShortcutsAsArray() {
|
public String[] getShortcutsAsArray() {
|
||||||
@@ -409,7 +361,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getPlaylistFolder() {
|
public String getPlaylistFolder() {
|
||||||
return properties.getProperty(KEY_PLAYLIST_FOLDER, DEFAULT_PLAYLIST_FOLDER);
|
return getProperty(KEY_PLAYLIST_FOLDER, DEFAULT_PLAYLIST_FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlaylistFolder(String playlistFolder) {
|
public void setPlaylistFolder(String playlistFolder) {
|
||||||
@@ -417,7 +369,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getMusicFileTypes() {
|
public String getMusicFileTypes() {
|
||||||
return properties.getProperty(KEY_MUSIC_FILE_TYPES, DEFAULT_MUSIC_FILE_TYPES);
|
return getProperty(KEY_MUSIC_FILE_TYPES, DEFAULT_MUSIC_FILE_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setMusicFileTypes(String fileTypes) {
|
public synchronized void setMusicFileTypes(String fileTypes) {
|
||||||
@@ -433,7 +385,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getVideoFileTypes() {
|
public String getVideoFileTypes() {
|
||||||
return properties.getProperty(KEY_VIDEO_FILE_TYPES, DEFAULT_VIDEO_FILE_TYPES);
|
return getProperty(KEY_VIDEO_FILE_TYPES, DEFAULT_VIDEO_FILE_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setVideoFileTypes(String fileTypes) {
|
public synchronized void setVideoFileTypes(String fileTypes) {
|
||||||
@@ -449,7 +401,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getCoverArtFileTypes() {
|
public String getCoverArtFileTypes() {
|
||||||
return properties.getProperty(KEY_COVER_ART_FILE_TYPES, DEFAULT_COVER_ART_FILE_TYPES);
|
return getProperty(KEY_COVER_ART_FILE_TYPES, DEFAULT_COVER_ART_FILE_TYPES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setCoverArtFileTypes(String fileTypes) {
|
public synchronized void setCoverArtFileTypes(String fileTypes) {
|
||||||
@@ -469,7 +421,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getWelcomeTitle() {
|
public String getWelcomeTitle() {
|
||||||
return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_TITLE, DEFAULT_WELCOME_TITLE));
|
return StringUtils.trimToNull(getProperty(KEY_WELCOME_TITLE, DEFAULT_WELCOME_TITLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWelcomeTitle(String title) {
|
public void setWelcomeTitle(String title) {
|
||||||
@@ -477,7 +429,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getWelcomeSubtitle() {
|
public String getWelcomeSubtitle() {
|
||||||
return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_SUBTITLE, DEFAULT_WELCOME_SUBTITLE));
|
return StringUtils.trimToNull(getProperty(KEY_WELCOME_SUBTITLE, DEFAULT_WELCOME_SUBTITLE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWelcomeSubtitle(String subtitle) {
|
public void setWelcomeSubtitle(String subtitle) {
|
||||||
@@ -485,7 +437,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getWelcomeMessage() {
|
public String getWelcomeMessage() {
|
||||||
return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_MESSAGE, DEFAULT_WELCOME_MESSAGE));
|
return StringUtils.trimToNull(getProperty(KEY_WELCOME_MESSAGE, DEFAULT_WELCOME_MESSAGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWelcomeMessage(String message) {
|
public void setWelcomeMessage(String message) {
|
||||||
@@ -493,7 +445,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getLoginMessage() {
|
public String getLoginMessage() {
|
||||||
return StringUtils.trimToNull(properties.getProperty(KEY_LOGIN_MESSAGE, DEFAULT_LOGIN_MESSAGE));
|
return StringUtils.trimToNull(getProperty(KEY_LOGIN_MESSAGE, DEFAULT_LOGIN_MESSAGE));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLoginMessage(String message) {
|
public void setLoginMessage(String message) {
|
||||||
@@ -586,7 +538,7 @@ public class SettingsService {
|
|||||||
* Returns the Podcast download folder.
|
* Returns the Podcast download folder.
|
||||||
*/
|
*/
|
||||||
public String getPodcastFolder() {
|
public String getPodcastFolder() {
|
||||||
return properties.getProperty(KEY_PODCAST_FOLDER, DEFAULT_PODCAST_FOLDER);
|
return getProperty(KEY_PODCAST_FOLDER, DEFAULT_PODCAST_FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -600,7 +552,7 @@ public class SettingsService {
|
|||||||
* @return The download bitrate limit in Kbit/s. Zero if unlimited.
|
* @return The download bitrate limit in Kbit/s. Zero if unlimited.
|
||||||
*/
|
*/
|
||||||
public long getDownloadBitrateLimit() {
|
public long getDownloadBitrateLimit() {
|
||||||
return Long.parseLong(properties.getProperty(KEY_DOWNLOAD_BITRATE_LIMIT, "" + DEFAULT_DOWNLOAD_BITRATE_LIMIT));
|
return Long.parseLong(getProperty(KEY_DOWNLOAD_BITRATE_LIMIT, "" + DEFAULT_DOWNLOAD_BITRATE_LIMIT));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -625,7 +577,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getDownsamplingCommand() {
|
public String getDownsamplingCommand() {
|
||||||
return properties.getProperty(KEY_DOWNSAMPLING_COMMAND, DEFAULT_DOWNSAMPLING_COMMAND);
|
return getProperty(KEY_DOWNSAMPLING_COMMAND, DEFAULT_DOWNSAMPLING_COMMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDownsamplingCommand(String command) {
|
public void setDownsamplingCommand(String command) {
|
||||||
@@ -633,7 +585,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getHlsCommand() {
|
public String getHlsCommand() {
|
||||||
return properties.getProperty(KEY_HLS_COMMAND, DEFAULT_HLS_COMMAND);
|
return getProperty(KEY_HLS_COMMAND, DEFAULT_HLS_COMMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHlsCommand(String command) {
|
public void setHlsCommand(String command) {
|
||||||
@@ -641,10 +593,10 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getJukeboxCommand() {
|
public String getJukeboxCommand() {
|
||||||
return properties.getProperty(KEY_JUKEBOX_COMMAND, DEFAULT_JUKEBOX_COMMAND);
|
return getProperty(KEY_JUKEBOX_COMMAND, DEFAULT_JUKEBOX_COMMAND);
|
||||||
}
|
}
|
||||||
public String getVideoImageCommand() {
|
public String getVideoImageCommand() {
|
||||||
return properties.getProperty(KEY_VIDEO_IMAGE_COMMAND, DEFAULT_VIDEO_IMAGE_COMMAND);
|
return getProperty(KEY_VIDEO_IMAGE_COMMAND, DEFAULT_VIDEO_IMAGE_COMMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRewriteUrlEnabled() {
|
public boolean isRewriteUrlEnabled() {
|
||||||
@@ -664,31 +616,31 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getLdapUrl() {
|
public String getLdapUrl() {
|
||||||
return properties.getProperty(KEY_LDAP_URL, DEFAULT_LDAP_URL);
|
return getProperty(KEY_LDAP_URL, DEFAULT_LDAP_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLdapUrl(String ldapUrl) {
|
public void setLdapUrl(String ldapUrl) {
|
||||||
properties.setProperty(KEY_LDAP_URL, ldapUrl);
|
setProperty(KEY_LDAP_URL, ldapUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLdapSearchFilter() {
|
public String getLdapSearchFilter() {
|
||||||
return properties.getProperty(KEY_LDAP_SEARCH_FILTER, DEFAULT_LDAP_SEARCH_FILTER);
|
return getProperty(KEY_LDAP_SEARCH_FILTER, DEFAULT_LDAP_SEARCH_FILTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLdapSearchFilter(String ldapSearchFilter) {
|
public void setLdapSearchFilter(String ldapSearchFilter) {
|
||||||
properties.setProperty(KEY_LDAP_SEARCH_FILTER, ldapSearchFilter);
|
setProperty(KEY_LDAP_SEARCH_FILTER, ldapSearchFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLdapManagerDn() {
|
public String getLdapManagerDn() {
|
||||||
return properties.getProperty(KEY_LDAP_MANAGER_DN, DEFAULT_LDAP_MANAGER_DN);
|
return getProperty(KEY_LDAP_MANAGER_DN, DEFAULT_LDAP_MANAGER_DN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLdapManagerDn(String ldapManagerDn) {
|
public void setLdapManagerDn(String ldapManagerDn) {
|
||||||
properties.setProperty(KEY_LDAP_MANAGER_DN, ldapManagerDn);
|
setProperty(KEY_LDAP_MANAGER_DN, ldapManagerDn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLdapManagerPassword() {
|
public String getLdapManagerPassword() {
|
||||||
String s = properties.getProperty(KEY_LDAP_MANAGER_PASSWORD, DEFAULT_LDAP_MANAGER_PASSWORD);
|
String s = getProperty(KEY_LDAP_MANAGER_PASSWORD, DEFAULT_LDAP_MANAGER_PASSWORD);
|
||||||
try {
|
try {
|
||||||
return StringUtil.utf8HexDecode(s);
|
return StringUtil.utf8HexDecode(s);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
@@ -703,7 +655,7 @@ public class SettingsService {
|
|||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
LOG.warn("Failed to encode LDAP manager password.", x);
|
LOG.warn("Failed to encode LDAP manager password.", x);
|
||||||
}
|
}
|
||||||
properties.setProperty(KEY_LDAP_MANAGER_PASSWORD, ldapManagerPassword);
|
setProperty(KEY_LDAP_MANAGER_PASSWORD, ldapManagerPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLdapAutoShadowing() {
|
public boolean isLdapAutoShadowing() {
|
||||||
@@ -762,43 +714,43 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getUrlRedirectFrom() {
|
public String getUrlRedirectFrom() {
|
||||||
return properties.getProperty(KEY_URL_REDIRECT_FROM, DEFAULT_URL_REDIRECT_FROM);
|
return getProperty(KEY_URL_REDIRECT_FROM, DEFAULT_URL_REDIRECT_FROM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrlRedirectFrom(String urlRedirectFrom) {
|
public void setUrlRedirectFrom(String urlRedirectFrom) {
|
||||||
properties.setProperty(KEY_URL_REDIRECT_FROM, urlRedirectFrom);
|
setProperty(KEY_URL_REDIRECT_FROM, urlRedirectFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UrlRedirectType getUrlRedirectType() {
|
public UrlRedirectType getUrlRedirectType() {
|
||||||
return UrlRedirectType.valueOf(properties.getProperty(KEY_URL_REDIRECT_TYPE, DEFAULT_URL_REDIRECT_TYPE.name()));
|
return UrlRedirectType.valueOf(getProperty(KEY_URL_REDIRECT_TYPE, DEFAULT_URL_REDIRECT_TYPE.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrlRedirectType(UrlRedirectType urlRedirectType) {
|
public void setUrlRedirectType(UrlRedirectType urlRedirectType) {
|
||||||
properties.setProperty(KEY_URL_REDIRECT_TYPE, urlRedirectType.name());
|
setProperty(KEY_URL_REDIRECT_TYPE, urlRedirectType.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrlRedirectContextPath() {
|
public String getUrlRedirectContextPath() {
|
||||||
return properties.getProperty(KEY_URL_REDIRECT_CONTEXT_PATH, DEFAULT_URL_REDIRECT_CONTEXT_PATH);
|
return getProperty(KEY_URL_REDIRECT_CONTEXT_PATH, DEFAULT_URL_REDIRECT_CONTEXT_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrlRedirectContextPath(String contextPath) {
|
public void setUrlRedirectContextPath(String contextPath) {
|
||||||
properties.setProperty(KEY_URL_REDIRECT_CONTEXT_PATH, contextPath);
|
setProperty(KEY_URL_REDIRECT_CONTEXT_PATH, contextPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrlRedirectCustomUrl() {
|
public String getUrlRedirectCustomUrl() {
|
||||||
return StringUtils.trimToNull(properties.getProperty(KEY_URL_REDIRECT_CUSTOM_URL, DEFAULT_URL_REDIRECT_CUSTOM_URL));
|
return StringUtils.trimToNull(getProperty(KEY_URL_REDIRECT_CUSTOM_URL, DEFAULT_URL_REDIRECT_CUSTOM_URL));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUrlRedirectCustomUrl(String customUrl) {
|
public void setUrlRedirectCustomUrl(String customUrl) {
|
||||||
properties.setProperty(KEY_URL_REDIRECT_CUSTOM_URL, customUrl);
|
setProperty(KEY_URL_REDIRECT_CUSTOM_URL, customUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServerId() {
|
public String getServerId() {
|
||||||
return properties.getProperty(KEY_SERVER_ID, DEFAULT_SERVER_ID);
|
return getProperty(KEY_SERVER_ID, DEFAULT_SERVER_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServerId(String serverId) {
|
public void setServerId(String serverId) {
|
||||||
properties.setProperty(KEY_SERVER_ID, serverId);
|
setProperty(KEY_SERVER_ID, serverId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSettingsChanged() {
|
public long getSettingsChanged() {
|
||||||
@@ -806,13 +758,13 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Date getLastScanned() {
|
public Date getLastScanned() {
|
||||||
String lastScanned = properties.getProperty(KEY_LAST_SCANNED);
|
String lastScanned = getProperty(KEY_LAST_SCANNED, null);
|
||||||
return lastScanned == null ? null : new Date(Long.parseLong(lastScanned));
|
return lastScanned == null ? null : new Date(Long.parseLong(lastScanned));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastScanned(Date date) {
|
public void setLastScanned(Date date) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
properties.remove(KEY_LAST_SCANNED);
|
setProperty(KEY_LAST_SCANNED, null);
|
||||||
} else {
|
} else {
|
||||||
setLong(KEY_LAST_SCANNED, date.getTime());
|
setLong(KEY_LAST_SCANNED, date.getTime());
|
||||||
}
|
}
|
||||||
@@ -848,9 +800,9 @@ public class SettingsService {
|
|||||||
* @return The locale.
|
* @return The locale.
|
||||||
*/
|
*/
|
||||||
public Locale getLocale() {
|
public Locale getLocale() {
|
||||||
String language = properties.getProperty(KEY_LOCALE_LANGUAGE, DEFAULT_LOCALE_LANGUAGE);
|
String language = getProperty(KEY_LOCALE_LANGUAGE, DEFAULT_LOCALE_LANGUAGE);
|
||||||
String country = properties.getProperty(KEY_LOCALE_COUNTRY, DEFAULT_LOCALE_COUNTRY);
|
String country = getProperty(KEY_LOCALE_COUNTRY, DEFAULT_LOCALE_COUNTRY);
|
||||||
String variant = properties.getProperty(KEY_LOCALE_VARIANT, DEFAULT_LOCALE_VARIANT);
|
String variant = getProperty(KEY_LOCALE_VARIANT, DEFAULT_LOCALE_VARIANT);
|
||||||
|
|
||||||
return new Locale(language, country, variant);
|
return new Locale(language, country, variant);
|
||||||
}
|
}
|
||||||
@@ -872,7 +824,7 @@ public class SettingsService {
|
|||||||
* @return The theme ID.
|
* @return The theme ID.
|
||||||
*/
|
*/
|
||||||
public String getThemeId() {
|
public String getThemeId() {
|
||||||
return properties.getProperty(KEY_THEME_ID, DEFAULT_THEME_ID);
|
return getProperty(KEY_THEME_ID, DEFAULT_THEME_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1309,11 +1261,11 @@ public class SettingsService {
|
|||||||
getPort());
|
getPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setProperty(String key, String value) {
|
private void setProperty(String key, Object value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
properties.remove(key);
|
configurationService.clearProperty(key);
|
||||||
} else {
|
} else {
|
||||||
properties.setProperty(key, value);
|
configurationService.setProperty(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1356,7 +1308,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSmtpServer() {
|
public String getSmtpServer() {
|
||||||
return properties.getProperty(KEY_SMTP_SERVER, DEFAULT_SMTP_SERVER);
|
return getProperty(KEY_SMTP_SERVER, DEFAULT_SMTP_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSmtpServer(String smtpServer) {
|
public void setSmtpServer(String smtpServer) {
|
||||||
@@ -1372,7 +1324,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSmtpEncryption() {
|
public String getSmtpEncryption() {
|
||||||
return properties.getProperty(KEY_SMTP_ENCRYPTION, DEFAULT_SMTP_ENCRYPTION);
|
return getProperty(KEY_SMTP_ENCRYPTION, DEFAULT_SMTP_ENCRYPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSmtpEncryption(String encryptionMethod) {
|
public void setSmtpEncryption(String encryptionMethod) {
|
||||||
@@ -1380,7 +1332,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSmtpUser() {
|
public String getSmtpUser() {
|
||||||
return properties.getProperty(KEY_SMTP_USER, DEFAULT_SMTP_USER);
|
return getProperty(KEY_SMTP_USER, DEFAULT_SMTP_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSmtpUser(String smtpUser) {
|
public void setSmtpUser(String smtpUser) {
|
||||||
@@ -1388,7 +1340,7 @@ public class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getSmtpPassword() {
|
public String getSmtpPassword() {
|
||||||
String s = properties.getProperty(KEY_SMTP_PASSWORD, DEFAULT_SMTP_PASSWORD);
|
String s = getProperty(KEY_SMTP_PASSWORD, DEFAULT_SMTP_PASSWORD);
|
||||||
try {
|
try {
|
||||||
return StringUtil.utf8HexDecode(s);
|
return StringUtil.utf8HexDecode(s);
|
||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
@@ -1402,14 +1354,18 @@ public class SettingsService {
|
|||||||
} catch (Exception x) {
|
} catch (Exception x) {
|
||||||
LOG.warn("Failed to encode Smtp password.", x);
|
LOG.warn("Failed to encode Smtp password.", x);
|
||||||
}
|
}
|
||||||
properties.setProperty(KEY_SMTP_PASSWORD, smtpPassword);
|
setProperty(KEY_SMTP_PASSWORD, smtpPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSmtpFrom() {
|
public String getSmtpFrom() {
|
||||||
return properties.getProperty(KEY_SMTP_FROM, DEFAULT_SMTP_FROM);
|
return getProperty(KEY_SMTP_FROM, DEFAULT_SMTP_FROM);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSmtpFrom(String smtpFrom) {
|
public void setSmtpFrom(String smtpFrom) {
|
||||||
setString(KEY_SMTP_FROM, smtpFrom);
|
setString(KEY_SMTP_FROM, smtpFrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setConfigurationService(ApacheCommonsConfigurationService configurationService) {
|
||||||
|
this.configurationService = configurationService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package org.libresonic.player.spring;
|
||||||
|
|
||||||
|
import org.apache.commons.configuration2.ImmutableConfiguration;
|
||||||
|
import org.libresonic.player.service.ApacheCommonsConfigurationService;
|
||||||
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
import org.springframework.web.context.ConfigurableWebApplicationContext;
|
||||||
|
|
||||||
|
public class AdditionalPropertySourceConfigurer implements
|
||||||
|
ApplicationContextInitializer<ConfigurableWebApplicationContext> {
|
||||||
|
|
||||||
|
public void initialize(ConfigurableWebApplicationContext ctx) {
|
||||||
|
|
||||||
|
ApacheCommonsConfigurationService configurationService = new ApacheCommonsConfigurationService();
|
||||||
|
ImmutableConfiguration snapshot = configurationService.getImmutableSnapshot();
|
||||||
|
|
||||||
|
PropertySource ps = new CommonsConfigurationPropertySource("libresonic-pre-init-configs", snapshot);
|
||||||
|
ctx.getEnvironment().getPropertySources().addLast(ps);
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package org.libresonic.player.spring;
|
||||||
|
|
||||||
|
import org.apache.commons.configuration2.ImmutableConfiguration;
|
||||||
|
import org.springframework.core.env.PropertySource;
|
||||||
|
|
||||||
|
public class CommonsConfigurationPropertySource extends PropertySource {
|
||||||
|
|
||||||
|
private final ImmutableConfiguration configuration;
|
||||||
|
|
||||||
|
public CommonsConfigurationPropertySource(String name, ImmutableConfiguration configuration) {
|
||||||
|
super(name);
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getProperty(String s) {
|
||||||
|
return configuration.getProperty(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,3 +7,7 @@ log4j.appender.A1=org.apache.log4j.ConsoleAppender
|
|||||||
# A1 uses PatternLayout.
|
# A1 uses PatternLayout.
|
||||||
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
|
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
|
||||||
log4j.appender.A1.layout.ConversionPattern=[%d{ISO8601}] %-5p %c - %m%n
|
log4j.appender.A1.layout.ConversionPattern=[%d{ISO8601}] %-5p %c - %m%n
|
||||||
|
|
||||||
|
# TODO remove this once https://issues.apache.org/jira/browse/CONFIGURATION-627 is fixed
|
||||||
|
log4j.logger.org.apache.commons.beanutils.FluentPropertyBeanIntrospector=ERROR, A1
|
||||||
|
log4j.additivity.org.apache.commons.beanutils.FluentPropertyBeanIntrospector=false
|
||||||
|
|||||||
@@ -86,12 +86,15 @@
|
|||||||
<property name="userCache" ref="userCache"/>
|
<property name="userCache" ref="userCache"/>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="configurationService" class="org.libresonic.player.service.ApacheCommonsConfigurationService" />
|
||||||
|
|
||||||
<bean id="settingsService" class="org.libresonic.player.service.SettingsService" init-method="init">
|
<bean id="settingsService" class="org.libresonic.player.service.SettingsService" init-method="init">
|
||||||
<property name="internetRadioDao" ref="internetRadioDao"/>
|
<property name="internetRadioDao" ref="internetRadioDao"/>
|
||||||
<property name="musicFolderDao" ref="musicFolderDao"/>
|
<property name="musicFolderDao" ref="musicFolderDao"/>
|
||||||
<property name="userDao" ref="userDao"/>
|
<property name="userDao" ref="userDao"/>
|
||||||
<property name="avatarDao" ref="avatarDao"/>
|
<property name="avatarDao" ref="avatarDao"/>
|
||||||
<property name="versionService" ref="versionService"/>
|
<property name="versionService" ref="versionService"/>
|
||||||
|
<property name="configurationService" ref="configurationService" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="mediaScannerService" class="org.libresonic.player.service.MediaScannerService" init-method="init" depends-on="metaDataParserFactory">
|
<bean id="mediaScannerService" class="org.libresonic.player.service.MediaScannerService" init-method="init" depends-on="metaDataParserFactory">
|
||||||
|
|||||||
@@ -16,6 +16,11 @@
|
|||||||
</param-value>
|
</param-value>
|
||||||
</context-param>
|
</context-param>
|
||||||
|
|
||||||
|
<context-param>
|
||||||
|
<param-name>contextInitializerClasses</param-name>
|
||||||
|
<param-value>org.libresonic.player.spring.AdditionalPropertySourceConfigurer</param-value>
|
||||||
|
</context-param>
|
||||||
|
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|||||||
Reference in New Issue
Block a user