Pastebin launched a little side project called HostCabi.net, check it out ;-)Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on Jan 9th, 2014  |  syntax: Java  |  size: 48.72 KB  |  hits: 61  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. /*
  2.  This file is part of Subsonic.
  3.  
  4.  Subsonic is free software: you can redistribute it and/or modify
  5.  it under the terms of the GNU General Public License as published by
  6.  the Free Software Foundation, either version 3 of the License, or
  7.  (at your option) any later version.
  8.  
  9.  Subsonic is distributed in the hope that it will be useful,
  10.  but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.  GNU General Public License for more details.
  13.  
  14.  You should have received a copy of the GNU General Public License
  15.  along with Subsonic.  If not, see <http://www.gnu.org/licenses/>.
  16.  
  17.  Copyright 2009 (C) Sindre Mehus
  18.  */
  19. package net.sourceforge.subsonic.service;
  20.  
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.OutputStream;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.Date;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.Locale;
  33. import java.util.Properties;
  34. import java.util.StringTokenizer;
  35. import java.util.concurrent.Executors;
  36. import java.util.concurrent.ScheduledExecutorService;
  37. import java.util.concurrent.ScheduledFuture;
  38. import java.util.concurrent.TimeUnit;
  39.  
  40. import org.apache.commons.io.IOUtils;
  41. import org.apache.commons.lang.StringUtils;
  42. import org.apache.http.client.HttpClient;
  43. import org.apache.http.client.ResponseHandler;
  44. import org.apache.http.client.methods.HttpGet;
  45. import org.apache.http.impl.client.BasicResponseHandler;
  46. import org.apache.http.impl.client.DefaultHttpClient;
  47. import org.apache.http.params.HttpConnectionParams;
  48.  
  49. import net.sourceforge.subsonic.Logger;
  50. import net.sourceforge.subsonic.dao.AvatarDao;
  51. import net.sourceforge.subsonic.dao.InternetRadioDao;
  52. import net.sourceforge.subsonic.dao.MusicFolderDao;
  53. import net.sourceforge.subsonic.dao.UserDao;
  54. import net.sourceforge.subsonic.domain.Avatar;
  55. import net.sourceforge.subsonic.domain.InternetRadio;
  56. import net.sourceforge.subsonic.domain.LicenseInfo;
  57. import net.sourceforge.subsonic.domain.MediaLibraryStatistics;
  58. import net.sourceforge.subsonic.domain.MusicFolder;
  59. import net.sourceforge.subsonic.domain.Theme;
  60. import net.sourceforge.subsonic.domain.UserSettings;
  61. import net.sourceforge.subsonic.util.FileUtil;
  62. import net.sourceforge.subsonic.util.StringUtil;
  63. import net.sourceforge.subsonic.util.Util;
  64.  
  65. /**
  66.  * Provides persistent storage of application settings and preferences.
  67.  *
  68.  * @author Sindre Mehus
  69.  */
  70. public class SettingsService {
  71.  
  72.     // Subsonic home directory.
  73.     private static final File SUBSONIC_HOME_WINDOWS = new File("c:/subsonic");
  74.     private static final File SUBSONIC_HOME_OTHER = new File("/var/subsonic");
  75.  
  76.     // Number of free trial days.
  77.     public static final long TRIAL_DAYS = 30L;
  78.  
  79.     // Global settings.
  80.     private static final String KEY_INDEX_STRING = "IndexString";
  81.     private static final String KEY_IGNORED_ARTICLES = "IgnoredArticles";
  82.     private static final String KEY_SHORTCUTS = "Shortcuts";
  83.     private static final String KEY_PLAYLIST_FOLDER = "PlaylistFolder";
  84.     private static final String KEY_MUSIC_FILE_TYPES = "MusicFileTypes";
  85.     private static final String KEY_VIDEO_FILE_TYPES = "VideoFileTypes";
  86.     private static final String KEY_COVER_ART_FILE_TYPES = "CoverArtFileTypes";
  87.     private static final String KEY_COVER_ART_LIMIT = "CoverArtLimit";
  88.     private static final String KEY_WELCOME_TITLE = "WelcomeTitle";
  89.     private static final String KEY_WELCOME_SUBTITLE = "WelcomeSubtitle";
  90.     private static final String KEY_WELCOME_MESSAGE = "WelcomeMessage2";
  91.     private static final String KEY_LOGIN_MESSAGE = "LoginMessage";
  92.     private static final String KEY_LOCALE_LANGUAGE = "LocaleLanguage";
  93.     private static final String KEY_LOCALE_COUNTRY = "LocaleCountry";
  94.     private static final String KEY_LOCALE_VARIANT = "LocaleVariant";
  95.     private static final String KEY_THEME_ID = "Theme";
  96.     private static final String KEY_INDEX_CREATION_INTERVAL = "IndexCreationInterval";
  97.     private static final String KEY_INDEX_CREATION_HOUR = "IndexCreationHour";
  98.     private static final String KEY_FAST_CACHE_ENABLED = "FastCacheEnabled";
  99.     private static final String KEY_PODCAST_UPDATE_INTERVAL = "PodcastUpdateInterval";
  100.     private static final String KEY_PODCAST_FOLDER = "PodcastFolder";
  101.     private static final String KEY_PODCAST_EPISODE_RETENTION_COUNT = "PodcastEpisodeRetentionCount";
  102.     private static final String KEY_PODCAST_EPISODE_DOWNLOAD_COUNT = "PodcastEpisodeDownloadCount";
  103.     private static final String KEY_DOWNLOAD_BITRATE_LIMIT = "DownloadBitrateLimit";
  104.     private static final String KEY_UPLOAD_BITRATE_LIMIT = "UploadBitrateLimit";
  105.     private static final String KEY_STREAM_PORT = "StreamPort";
  106.     private static final String KEY_LICENSE_EMAIL = "LicenseEmail";
  107.     private static final String KEY_LICENSE_CODE = "LicenseCode";
  108.     private static final String KEY_LICENSE_DATE = "LicenseDate";
  109.     private static final String KEY_DOWNSAMPLING_COMMAND = "DownsamplingCommand3";
  110.     private static final String KEY_HLS_COMMAND = "HlsCommand2";
  111.     private static final String KEY_JUKEBOX_COMMAND = "JukeboxCommand";
  112.     private static final String KEY_REWRITE_URL = "RewriteUrl";
  113.     private static final String KEY_LDAP_ENABLED = "LdapEnabled";
  114.     private static final String KEY_LDAP_URL = "LdapUrl";
  115.     private static final String KEY_LDAP_MANAGER_DN = "LdapManagerDn";
  116.     private static final String KEY_LDAP_MANAGER_PASSWORD = "LdapManagerPassword";
  117.     private static final String KEY_LDAP_SEARCH_FILTER = "LdapSearchFilter";
  118.     private static final String KEY_LDAP_AUTO_SHADOWING = "LdapAutoShadowing";
  119.     private static final String KEY_GETTING_STARTED_ENABLED = "GettingStartedEnabled";
  120.     private static final String KEY_PORT_FORWARDING_ENABLED = "PortForwardingEnabled";
  121.     private static final String KEY_PORT = "Port";
  122.     private static final String KEY_HTTPS_PORT = "HttpsPort";
  123.     private static final String KEY_URL_REDIRECTION_ENABLED = "UrlRedirectionEnabled";
  124.     private static final String KEY_URL_REDIRECT_FROM = "UrlRedirectFrom";
  125.     private static final String KEY_URL_REDIRECT_CONTEXT_PATH = "UrlRedirectContextPath";
  126.     private static final String KEY_SERVER_ID = "ServerId";
  127.     private static final String KEY_SETTINGS_CHANGED = "SettingsChanged";
  128.     private static final String KEY_LAST_SCANNED = "LastScanned";
  129.     private static final String KEY_ORGANIZE_BY_FOLDER_STRUCTURE = "OrganizeByFolderStructure";
  130.     private static final String KEY_SORT_ALBUMS_BY_YEAR = "SortAlbumsByYear";
  131.     private static final String KEY_MEDIA_LIBRARY_STATISTICS = "MediaLibraryStatistics";
  132.     private static final String KEY_TRIAL_EXPIRES = "TrialExpires";
  133.     private static final String KEY_DLNA_ENABLED = "DlnaEnabled";
  134.  
  135.     // Default values.
  136.     private static final String DEFAULT_INDEX_STRING = "A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ)";
  137.     private static final String DEFAULT_IGNORED_ARTICLES = "The El La Los Las Le Les";
  138.     private static final String DEFAULT_SHORTCUTS = "New Incoming Podcast";
  139.     private static final String DEFAULT_PLAYLIST_FOLDER = Util.getDefaultPlaylistFolder();
  140.     private static final String DEFAULT_MUSIC_FILE_TYPES = "mp3 ogg oga aac m4a flac wav wma aif aiff ape mpc shn";
  141.     private static final String DEFAULT_VIDEO_FILE_TYPES = "flv avi mpg mpeg mp4 m4v mkv mov wmv ogv divx m2ts";
  142.     private static final String DEFAULT_COVER_ART_FILE_TYPES = "cover.jpg folder.jpg jpg jpeg gif png";
  143.     private static final int DEFAULT_COVER_ART_LIMIT = 50;
  144.     private static final String DEFAULT_WELCOME_TITLE = "Welcome to Subsonic!";
  145.     private static final String DEFAULT_WELCOME_SUBTITLE = null;
  146.     private static final String DEFAULT_WELCOME_MESSAGE = "__Welcome to Subsonic!__\n" +
  147.             "\\\\ \\\\\n" +
  148.             "Subsonic is a free, web-based media streamer, providing ubiquitous access to your music. \n" +
  149.             "\\\\ \\\\\n" +
  150.             "Use it to share your music with friends, or to listen to your own music while at work. You can stream to multiple " +
  151.             "players simultaneously, for instance to one player in your kitchen and another in your living room.\n" +
  152.             "\\\\ \\\\\n" +
  153.             "To change or remove this message, log in with administrator rights and go to {link:Settings > General|generalSettings.view}.";
  154.     private static final String DEFAULT_LOGIN_MESSAGE = null;
  155.     private static final String DEFAULT_LOCALE_LANGUAGE = "en";
  156.     private static final String DEFAULT_LOCALE_COUNTRY = "";
  157.     private static final String DEFAULT_LOCALE_VARIANT = "";
  158.     private static final String DEFAULT_THEME_ID = "default";
  159.     private static final int DEFAULT_INDEX_CREATION_INTERVAL = 1;
  160.     private static final int DEFAULT_INDEX_CREATION_HOUR = 3;
  161.     private static final boolean DEFAULT_FAST_CACHE_ENABLED = false;
  162.     private static final int DEFAULT_PODCAST_UPDATE_INTERVAL = 24;
  163.     private static final String DEFAULT_PODCAST_FOLDER = Util.getDefaultPodcastFolder();
  164.     private static final int DEFAULT_PODCAST_EPISODE_RETENTION_COUNT = 10;
  165.     private static final int DEFAULT_PODCAST_EPISODE_DOWNLOAD_COUNT = 1;
  166.     private static final long DEFAULT_DOWNLOAD_BITRATE_LIMIT = 0;
  167.     private static final long DEFAULT_UPLOAD_BITRATE_LIMIT = 0;
  168.     private static final long DEFAULT_STREAM_PORT = 0;
  169.     private static final String DEFAULT_LICENSE_EMAIL = null;
  170.     private static final String DEFAULT_LICENSE_CODE = null;
  171.     private static final String DEFAULT_LICENSE_DATE = null;
  172.     private static final String DEFAULT_DOWNSAMPLING_COMMAND = "ffmpeg -i %s -ab %bk -v 0 -f mp3 -";
  173.     private static final String DEFAULT_HLS_COMMAND = "ffmpeg -ss %o -t %d -i %s -async 1 -b %bk -s %wx%h -ar 44100 -ac 2 -v 0 -f mpegts -vcodec libx264 -preset superfast -acodec libmp3lame -threads 0 -";
  174.     private static final String DEFAULT_JUKEBOX_COMMAND = "ffmpeg -ss %o -i %s -v 0 -f au -";
  175.     private static final boolean DEFAULT_REWRITE_URL = true;
  176.     private static final boolean DEFAULT_LDAP_ENABLED = false;
  177.     private static final String DEFAULT_LDAP_URL = "ldap://host.domain.com:389/cn=Users,dc=domain,dc=com";
  178.     private static final String DEFAULT_LDAP_MANAGER_DN = null;
  179.     private static final String DEFAULT_LDAP_MANAGER_PASSWORD = null;
  180.     private static final String DEFAULT_LDAP_SEARCH_FILTER = "(sAMAccountName={0})";
  181.     private static final boolean DEFAULT_LDAP_AUTO_SHADOWING = false;
  182.     private static final boolean DEFAULT_PORT_FORWARDING_ENABLED = false;
  183.     private static final boolean DEFAULT_GETTING_STARTED_ENABLED = true;
  184.     private static final int DEFAULT_PORT = 80;
  185.     private static final int DEFAULT_HTTPS_PORT = 0;
  186.     private static final boolean DEFAULT_URL_REDIRECTION_ENABLED = false;
  187.     private static final String DEFAULT_URL_REDIRECT_FROM = "yourname";
  188.     private static final String DEFAULT_URL_REDIRECT_CONTEXT_PATH = null;
  189.     private static final String DEFAULT_SERVER_ID = null;
  190.     private static final long DEFAULT_SETTINGS_CHANGED = 0L;
  191.     private static final boolean DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE = true;
  192.     private static final boolean DEFAULT_SORT_ALBUMS_BY_YEAR = true;
  193.     private static final String DEFAULT_MEDIA_LIBRARY_STATISTICS = "0 0 0 0 0";
  194.     private static final String DEFAULT_TRIAL_EXPIRES = null;
  195.     private static final boolean DEFAULT_DLNA_ENABLED = true;
  196.  
  197.     // Array of obsolete keys.  Used to clean property file.
  198.     private static final List<String> OBSOLETE_KEYS = Arrays.asList("PortForwardingPublicPort", "PortForwardingLocalPort",
  199.             "DownsamplingCommand", "DownsamplingCommand2", "AutoCoverBatch", "MusicMask", "VideoMask", "CoverArtMask, HlsCommand",
  200.             "UrlRedirectTrialExpires", "VideoTrialExpires");
  201.  
  202.     private static final String LOCALES_FILE = "/net/sourceforge/subsonic/i18n/locales.txt";
  203.     private static final String THEMES_FILE = "/net/sourceforge/subsonic/theme/themes.txt";
  204.  
  205.     private static final Logger LOG = Logger.getLogger(SettingsService.class);
  206.  
  207.     private Properties properties = new Properties();
  208.     private List<Theme> themes;
  209.     private List<Locale> locales;
  210.     private InternetRadioDao internetRadioDao;
  211.     private MusicFolderDao musicFolderDao;
  212.     private UserDao userDao;
  213.     private AvatarDao avatarDao;
  214.     private VersionService versionService;
  215.  
  216.     private String[] cachedCoverArtFileTypesArray;
  217.     private String[] cachedMusicFileTypesArray;
  218.     private String[] cachedVideoFileTypesArray;
  219.     private List<MusicFolder> cachedMusicFolders;
  220.    
  221.     private static File subsonicHome;
  222.  
  223.     private boolean licenseValidated = true;
  224.     private Date licenseExpires;
  225.     private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
  226.     private ScheduledFuture<?> licenseValidationFuture;
  227.     private static final long LICENSE_VALIDATION_DELAY_HOURS = 12;
  228.  
  229.     public SettingsService() {
  230.         File propertyFile = getPropertyFile();
  231.  
  232.         if (propertyFile.exists()) {
  233.             FileInputStream in = null;
  234.             try {
  235.                 in = new FileInputStream(propertyFile);
  236.                 properties.load(in);
  237.             } catch (Exception x) {
  238.                 LOG.error("Unable to read from property file.", x);
  239.             } finally {
  240.                 IOUtils.closeQuietly(in);
  241.             }
  242.  
  243.             // Remove obsolete properties.
  244.             for (Iterator<Object> iterator = properties.keySet().iterator(); iterator.hasNext();) {
  245.                 String key = (String) iterator.next();
  246.                 if (OBSOLETE_KEYS.contains(key)) {
  247.                     LOG.debug("Removing obsolete property [" + key + ']');
  248.                     iterator.remove();
  249.                 }
  250.             }
  251.         }
  252.  
  253.         // Start trial.
  254.         if (getTrialExpires() == null) {
  255.             Date expiryDate = new Date(System.currentTimeMillis() + TRIAL_DAYS * 24L * 3600L * 1000L);
  256.             setTrialExpires(expiryDate);
  257.         }
  258.  
  259.         save(false);
  260.     }
  261.  
  262.     /**
  263.      * Register in service locator so that non-Spring objects can access me.
  264.      * This method is invoked automatically by Spring.
  265.      */
  266.     public void init() {
  267.         ServiceLocator.setSettingsService(this);
  268.         scheduleLicenseValidation();
  269.     }
  270.  
  271.     public void save() {
  272.         save(true);
  273.     }
  274.  
  275.     public void save(boolean updateChangedDate) {
  276.         if (updateChangedDate) {
  277.             setProperty(KEY_SETTINGS_CHANGED, String.valueOf(System.currentTimeMillis()));
  278.         }
  279.  
  280.         OutputStream out = null;
  281.         try {
  282.             out = new FileOutputStream(getPropertyFile());
  283.             properties.store(out, "Subsonic preferences.  NOTE: This file is automatically generated.");
  284.         } catch (Exception x) {
  285.             LOG.error("Unable to write to property file.", x);
  286.         } finally {
  287.             IOUtils.closeQuietly(out);
  288.         }
  289.     }
  290.  
  291.     private File getPropertyFile() {
  292.         return new File(getSubsonicHome(), "subsonic.properties");
  293.     }
  294.  
  295.     /**
  296.      * Returns the Subsonic home directory.
  297.      *
  298.      * @return The Subsonic home directory, if it exists.
  299.      * @throws RuntimeException If directory doesn't exist.
  300.      */
  301.     public static synchronized File getSubsonicHome() {
  302.  
  303.         if (subsonicHome != null) {
  304.             return subsonicHome;
  305.         }
  306.  
  307.         File home;
  308.  
  309.         String overrideHome = System.getProperty("subsonic.home");
  310.         if (overrideHome != null) {
  311.             home = new File(overrideHome);
  312.         } else {
  313.             boolean isWindows = System.getProperty("os.name", "Windows").toLowerCase().startsWith("windows");
  314.             home = isWindows ? SUBSONIC_HOME_WINDOWS : SUBSONIC_HOME_OTHER;
  315.         }
  316.  
  317.         // Attempt to create home directory if it doesn't exist.
  318.         if (!home.exists() || !home.isDirectory()) {
  319.             boolean success = home.mkdirs();
  320.             if (success) {
  321.                 subsonicHome = home;
  322.             } else {
  323.                 String message = "The directory " + home + " does not exist. Please create it and make it writable. " +
  324.                         "(You can override the directory location by specifying -Dsubsonic.home=... when " +
  325.                         "starting the servlet container.)";
  326.                 System.err.println("ERROR: " + message);
  327.             }
  328.         } else {
  329.             subsonicHome = home;
  330.         }
  331.  
  332.         return home;
  333.     }
  334.  
  335.     private boolean getBoolean(String key, boolean defaultValue) {
  336.         return Boolean.valueOf(properties.getProperty(key, String.valueOf(defaultValue)));
  337.     }
  338.  
  339.     private void setBoolean(String key, boolean value) {
  340.         setProperty(key, String.valueOf(value));
  341.     }
  342.  
  343.     private String getString(String key, String defaultValue) {
  344.         return properties.getProperty(key, defaultValue);
  345.     }
  346.  
  347.     private void setString(String key, String value) {
  348.         setProperty(key, value);
  349.     }
  350.  
  351.     public String getIndexString() {
  352.         return properties.getProperty(KEY_INDEX_STRING, DEFAULT_INDEX_STRING);
  353.     }
  354.  
  355.     public void setIndexString(String indexString) {
  356.         setProperty(KEY_INDEX_STRING, indexString);
  357.     }
  358.  
  359.     public String getIgnoredArticles() {
  360.         return properties.getProperty(KEY_IGNORED_ARTICLES, DEFAULT_IGNORED_ARTICLES);
  361.     }
  362.  
  363.     public String[] getIgnoredArticlesAsArray() {
  364.         return getIgnoredArticles().split("\\s+");
  365.     }
  366.  
  367.     public void setIgnoredArticles(String ignoredArticles) {
  368.         setProperty(KEY_IGNORED_ARTICLES, ignoredArticles);
  369.     }
  370.  
  371.     public String getShortcuts() {
  372.         return properties.getProperty(KEY_SHORTCUTS, DEFAULT_SHORTCUTS);
  373.     }
  374.  
  375.     public String[] getShortcutsAsArray() {
  376.         return StringUtil.split(getShortcuts());
  377.     }
  378.  
  379.     public void setShortcuts(String shortcuts) {
  380.         setProperty(KEY_SHORTCUTS, shortcuts);
  381.     }
  382.  
  383.     public String getPlaylistFolder() {
  384.         return properties.getProperty(KEY_PLAYLIST_FOLDER, DEFAULT_PLAYLIST_FOLDER);
  385.     }
  386.  
  387.     public void setPlaylistFolder(String playlistFolder) {
  388.         setProperty(KEY_PLAYLIST_FOLDER, playlistFolder);
  389.     }
  390.  
  391.     public String getMusicFileTypes() {
  392.         return properties.getProperty(KEY_MUSIC_FILE_TYPES, DEFAULT_MUSIC_FILE_TYPES);
  393.     }
  394.  
  395.     public synchronized void setMusicFileTypes(String fileTypes) {
  396.         setProperty(KEY_MUSIC_FILE_TYPES, fileTypes);
  397.         cachedMusicFileTypesArray = null;
  398.     }
  399.  
  400.     public synchronized String[] getMusicFileTypesAsArray() {
  401.         if (cachedMusicFileTypesArray == null) {
  402.             cachedMusicFileTypesArray = toStringArray(getMusicFileTypes());
  403.         }
  404.         return cachedMusicFileTypesArray;
  405.     }
  406.  
  407.     public String getVideoFileTypes() {
  408.         return properties.getProperty(KEY_VIDEO_FILE_TYPES, DEFAULT_VIDEO_FILE_TYPES);
  409.     }
  410.  
  411.     public synchronized void setVideoFileTypes(String fileTypes) {
  412.         setProperty(KEY_VIDEO_FILE_TYPES, fileTypes);
  413.         cachedVideoFileTypesArray = null;
  414.     }
  415.  
  416.     public synchronized String[] getVideoFileTypesAsArray() {
  417.         if (cachedVideoFileTypesArray == null) {
  418.             cachedVideoFileTypesArray = toStringArray(getVideoFileTypes());
  419.         }
  420.         return cachedVideoFileTypesArray;
  421.     }
  422.  
  423.     public String getCoverArtFileTypes() {
  424.         return properties.getProperty(KEY_COVER_ART_FILE_TYPES, DEFAULT_COVER_ART_FILE_TYPES);
  425.     }
  426.  
  427.     public synchronized void setCoverArtFileTypes(String fileTypes) {
  428.         setProperty(KEY_COVER_ART_FILE_TYPES, fileTypes);
  429.         cachedCoverArtFileTypesArray = null;
  430.     }
  431.  
  432.     public synchronized String[] getCoverArtFileTypesAsArray() {
  433.         if (cachedCoverArtFileTypesArray == null) {
  434.             cachedCoverArtFileTypesArray = toStringArray(getCoverArtFileTypes());
  435.         }
  436.         return cachedCoverArtFileTypesArray;
  437.     }
  438.  
  439.     public int getCoverArtLimit() {
  440.         return Integer.parseInt(properties.getProperty(KEY_COVER_ART_LIMIT, "" + DEFAULT_COVER_ART_LIMIT));
  441.     }
  442.  
  443.     public void setCoverArtLimit(int limit) {
  444.         setProperty(KEY_COVER_ART_LIMIT, "" + limit);
  445.     }
  446.  
  447.     public String getWelcomeTitle() {
  448.         return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_TITLE, DEFAULT_WELCOME_TITLE));
  449.     }
  450.  
  451.     public void setWelcomeTitle(String title) {
  452.         setProperty(KEY_WELCOME_TITLE, title);
  453.     }
  454.  
  455.     public String getWelcomeSubtitle() {
  456.         return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_SUBTITLE, DEFAULT_WELCOME_SUBTITLE));
  457.     }
  458.  
  459.     public void setWelcomeSubtitle(String subtitle) {
  460.         setProperty(KEY_WELCOME_SUBTITLE, subtitle);
  461.     }
  462.  
  463.     public String getWelcomeMessage() {
  464.         return StringUtils.trimToNull(properties.getProperty(KEY_WELCOME_MESSAGE, DEFAULT_WELCOME_MESSAGE));
  465.     }
  466.  
  467.     public void setWelcomeMessage(String message) {
  468.         setProperty(KEY_WELCOME_MESSAGE, message);
  469.     }
  470.  
  471.     public String getLoginMessage() {
  472.         return StringUtils.trimToNull(properties.getProperty(KEY_LOGIN_MESSAGE, DEFAULT_LOGIN_MESSAGE));
  473.     }
  474.  
  475.     public void setLoginMessage(String message) {
  476.         setProperty(KEY_LOGIN_MESSAGE, message);
  477.     }
  478.  
  479.     /**
  480.      * Returns the number of days between automatic index creation, of -1 if automatic index
  481.      * creation is disabled.
  482.      */
  483.     public int getIndexCreationInterval() {
  484.         return Integer.parseInt(properties.getProperty(KEY_INDEX_CREATION_INTERVAL, "" + DEFAULT_INDEX_CREATION_INTERVAL));
  485.     }
  486.  
  487.     /**
  488.      * Sets the number of days between automatic index creation, of -1 if automatic index
  489.      * creation is disabled.
  490.      */
  491.     public void setIndexCreationInterval(int days) {
  492.         setProperty(KEY_INDEX_CREATION_INTERVAL, String.valueOf(days));
  493.     }
  494.  
  495.     /**
  496.      * Returns the hour of day (0 - 23) when automatic index creation should run.
  497.      */
  498.     public int getIndexCreationHour() {
  499.         return Integer.parseInt(properties.getProperty(KEY_INDEX_CREATION_HOUR, String.valueOf(DEFAULT_INDEX_CREATION_HOUR)));
  500.     }
  501.  
  502.     /**
  503.      * Sets the hour of day (0 - 23) when automatic index creation should run.
  504.      */
  505.     public void setIndexCreationHour(int hour) {
  506.         setProperty(KEY_INDEX_CREATION_HOUR, String.valueOf(hour));
  507.     }
  508.  
  509.     public boolean isFastCacheEnabled() {
  510.         return getBoolean(KEY_FAST_CACHE_ENABLED, DEFAULT_FAST_CACHE_ENABLED);
  511.     }
  512.  
  513.     public void setFastCacheEnabled(boolean enabled) {
  514.         setBoolean(KEY_FAST_CACHE_ENABLED, enabled);
  515.     }
  516.  
  517.     /**
  518.      * Returns the number of hours between Podcast updates, of -1 if automatic updates
  519.      * are disabled.
  520.      */
  521.     public int getPodcastUpdateInterval() {
  522.         return Integer.parseInt(properties.getProperty(KEY_PODCAST_UPDATE_INTERVAL, String.valueOf(DEFAULT_PODCAST_UPDATE_INTERVAL)));
  523.     }
  524.  
  525.     /**
  526.      * Sets the number of hours between Podcast updates, of -1 if automatic updates
  527.      * are disabled.
  528.      */
  529.     public void setPodcastUpdateInterval(int hours) {
  530.         setProperty(KEY_PODCAST_UPDATE_INTERVAL, String.valueOf(hours));
  531.     }
  532.  
  533.     /**
  534.      * Returns the number of Podcast episodes to keep (-1 to keep all).
  535.      */
  536.     public int getPodcastEpisodeRetentionCount() {
  537.         return Integer.parseInt(properties.getProperty(KEY_PODCAST_EPISODE_RETENTION_COUNT, String.valueOf(DEFAULT_PODCAST_EPISODE_RETENTION_COUNT)));
  538.     }
  539.  
  540.     /**
  541.      * Sets the number of Podcast episodes to keep (-1 to keep all).
  542.      */
  543.     public void setPodcastEpisodeRetentionCount(int count) {
  544.         setProperty(KEY_PODCAST_EPISODE_RETENTION_COUNT, String.valueOf(count));
  545.     }
  546.  
  547.     /**
  548.      * Returns the number of Podcast episodes to download (-1 to download all).
  549.      */
  550.     public int getPodcastEpisodeDownloadCount() {
  551.         return Integer.parseInt(properties.getProperty(KEY_PODCAST_EPISODE_DOWNLOAD_COUNT, String.valueOf(DEFAULT_PODCAST_EPISODE_DOWNLOAD_COUNT)));
  552.     }
  553.  
  554.     /**
  555.      * Sets the number of Podcast episodes to download (-1 to download all).
  556.      */
  557.     public void setPodcastEpisodeDownloadCount(int count) {
  558.         setProperty(KEY_PODCAST_EPISODE_DOWNLOAD_COUNT, String.valueOf(count));
  559.     }
  560.  
  561.     /**
  562.      * Returns the Podcast download folder.
  563.      */
  564.     public String getPodcastFolder() {
  565.         return properties.getProperty(KEY_PODCAST_FOLDER, DEFAULT_PODCAST_FOLDER);
  566.     }
  567.  
  568.     /**
  569.      * Sets the Podcast download folder.
  570.      */
  571.     public void setPodcastFolder(String folder) {
  572.         setProperty(KEY_PODCAST_FOLDER, folder);
  573.     }
  574.  
  575.     /**
  576.      * @return The download bitrate limit in Kbit/s. Zero if unlimited.
  577.      */
  578.     public long getDownloadBitrateLimit() {
  579.         return Long.parseLong(properties.getProperty(KEY_DOWNLOAD_BITRATE_LIMIT, "" + DEFAULT_DOWNLOAD_BITRATE_LIMIT));
  580.     }
  581.  
  582.     /**
  583.      * @param limit The download bitrate limit in Kbit/s. Zero if unlimited.
  584.      */
  585.     public void setDownloadBitrateLimit(long limit) {
  586.         setProperty(KEY_DOWNLOAD_BITRATE_LIMIT, "" + limit);
  587.     }
  588.  
  589.     /**
  590.      * @return The upload bitrate limit in Kbit/s. Zero if unlimited.
  591.      */
  592.     public long getUploadBitrateLimit() {
  593.         return Long.parseLong(properties.getProperty(KEY_UPLOAD_BITRATE_LIMIT, "" + DEFAULT_UPLOAD_BITRATE_LIMIT));
  594.     }
  595.  
  596.     /**
  597.      * @param limit The upload bitrate limit in Kbit/s. Zero if unlimited.
  598.      */
  599.     public void setUploadBitrateLimit(long limit) {
  600.         setProperty(KEY_UPLOAD_BITRATE_LIMIT, "" + limit);
  601.     }
  602.  
  603.     /**
  604.      * @return The non-SSL stream port. Zero if disabled.
  605.      */
  606.     public int getStreamPort() {
  607.         return Integer.parseInt(properties.getProperty(KEY_STREAM_PORT, "" + DEFAULT_STREAM_PORT));
  608.     }
  609.  
  610.     /**
  611.      * @param port The non-SSL stream port. Zero if disabled.
  612.      */
  613.     public void setStreamPort(int port) {
  614.         setProperty(KEY_STREAM_PORT, "" + port);
  615.     }
  616.  
  617.     public String getLicenseEmail() {
  618.         return properties.getProperty(KEY_LICENSE_EMAIL, DEFAULT_LICENSE_EMAIL);
  619.     }
  620.  
  621.     public void setLicenseEmail(String email) {
  622.         setProperty(KEY_LICENSE_EMAIL, email);
  623.     }
  624.  
  625.     public String getLicenseCode() {
  626.         return properties.getProperty(KEY_LICENSE_CODE, DEFAULT_LICENSE_CODE);
  627.     }
  628.  
  629.     public void setLicenseCode(String code) {
  630.         setProperty(KEY_LICENSE_CODE, code);
  631.     }
  632.  
  633.     public Date getLicenseDate() {
  634.         String value = properties.getProperty(KEY_LICENSE_DATE, DEFAULT_LICENSE_DATE);
  635.         return value == null ? null : new Date(Long.parseLong(value));
  636.     }
  637.  
  638.     public void setLicenseDate(Date date) {
  639.         String value = (date == null ? null : String.valueOf(date.getTime()));
  640.         setProperty(KEY_LICENSE_DATE, value);
  641.     }
  642.  
  643.     public boolean isLicenseValid() {
  644.         return isLicenseValid(getLicenseEmail(), getLicenseCode()) && licenseValidated;
  645.     }
  646.  
  647.     public boolean isLicenseValid(String email, String license) {
  648.         if (email == null || license == null) {
  649.             return false;
  650.         }
  651.         return license.equalsIgnoreCase(StringUtil.md5Hex(email.toLowerCase()));
  652.     }
  653.  
  654.     public LicenseInfo getLicenseInfo() {
  655.         Date trialExpires = getTrialExpires();
  656.         Date now = new Date();
  657.         boolean trialValid = trialExpires.after(now);
  658.         long trialDaysLeft = trialValid ? (trialExpires.getTime() - now.getTime()) / (24L * 3600L * 1000L) : 0L;
  659.  
  660.         return new LicenseInfo(getLicenseEmail(), isLicenseValid(), trialExpires, trialDaysLeft, licenseExpires);
  661.     }
  662.  
  663.     public String getDownsamplingCommand() {
  664.         return properties.getProperty(KEY_DOWNSAMPLING_COMMAND, DEFAULT_DOWNSAMPLING_COMMAND);
  665.     }
  666.  
  667.     public void setDownsamplingCommand(String command) {
  668.         setProperty(KEY_DOWNSAMPLING_COMMAND, command);
  669.     }
  670.  
  671.     public String getHlsCommand() {
  672.         return properties.getProperty(KEY_HLS_COMMAND, DEFAULT_HLS_COMMAND);
  673.     }
  674.  
  675.     public void setHlsCommand(String command) {
  676.         setProperty(KEY_HLS_COMMAND, command);
  677.     }
  678.  
  679.     public String getJukeboxCommand() {
  680.         return properties.getProperty(KEY_JUKEBOX_COMMAND, DEFAULT_JUKEBOX_COMMAND);
  681.     }
  682.  
  683.     public boolean isRewriteUrlEnabled() {
  684.         return getBoolean(KEY_REWRITE_URL, DEFAULT_REWRITE_URL);
  685.     }
  686.  
  687.     public void setRewriteUrlEnabled(boolean rewriteUrl) {
  688.         setBoolean(KEY_REWRITE_URL, rewriteUrl);
  689.     }
  690.  
  691.     public boolean isLdapEnabled() {
  692.         return getBoolean(KEY_LDAP_ENABLED, DEFAULT_LDAP_ENABLED);
  693.     }
  694.  
  695.     public void setLdapEnabled(boolean ldapEnabled) {
  696.         setBoolean(KEY_LDAP_ENABLED, ldapEnabled);
  697.     }
  698.  
  699.     public String getLdapUrl() {
  700.         return properties.getProperty(KEY_LDAP_URL, DEFAULT_LDAP_URL);
  701.     }
  702.  
  703.     public void setLdapUrl(String ldapUrl) {
  704.         properties.setProperty(KEY_LDAP_URL, ldapUrl);
  705.     }
  706.  
  707.     public String getLdapSearchFilter() {
  708.         return properties.getProperty(KEY_LDAP_SEARCH_FILTER, DEFAULT_LDAP_SEARCH_FILTER);
  709.     }
  710.  
  711.     public void setLdapSearchFilter(String ldapSearchFilter) {
  712.         properties.setProperty(KEY_LDAP_SEARCH_FILTER, ldapSearchFilter);
  713.     }
  714.  
  715.     public String getLdapManagerDn() {
  716.         return properties.getProperty(KEY_LDAP_MANAGER_DN, DEFAULT_LDAP_MANAGER_DN);
  717.     }
  718.  
  719.     public void setLdapManagerDn(String ldapManagerDn) {
  720.         properties.setProperty(KEY_LDAP_MANAGER_DN, ldapManagerDn);
  721.     }
  722.  
  723.     public String getLdapManagerPassword() {
  724.         String s = properties.getProperty(KEY_LDAP_MANAGER_PASSWORD, DEFAULT_LDAP_MANAGER_PASSWORD);
  725.         try {
  726.             return StringUtil.utf8HexDecode(s);
  727.         } catch (Exception x) {
  728.             LOG.warn("Failed to decode LDAP manager password.", x);
  729.             return s;
  730.         }
  731.     }
  732.  
  733.     public void setLdapManagerPassword(String ldapManagerPassword) {
  734.         try {
  735.             ldapManagerPassword = StringUtil.utf8HexEncode(ldapManagerPassword);
  736.         } catch (Exception x) {
  737.             LOG.warn("Failed to encode LDAP manager password.", x);
  738.         }
  739.         properties.setProperty(KEY_LDAP_MANAGER_PASSWORD, ldapManagerPassword);
  740.     }
  741.  
  742.     public boolean isLdapAutoShadowing() {
  743.         return getBoolean(KEY_LDAP_AUTO_SHADOWING, DEFAULT_LDAP_AUTO_SHADOWING);
  744.     }
  745.  
  746.     public void setLdapAutoShadowing(boolean ldapAutoShadowing) {
  747.         setBoolean(KEY_LDAP_AUTO_SHADOWING, ldapAutoShadowing);
  748.     }
  749.  
  750.     public boolean isGettingStartedEnabled() {
  751.         return getBoolean(KEY_GETTING_STARTED_ENABLED, DEFAULT_GETTING_STARTED_ENABLED);
  752.     }
  753.  
  754.     public void setGettingStartedEnabled(boolean isGettingStartedEnabled) {
  755.         setBoolean(KEY_GETTING_STARTED_ENABLED, isGettingStartedEnabled);
  756.     }
  757.  
  758.     public boolean isPortForwardingEnabled() {
  759.         return getBoolean(KEY_PORT_FORWARDING_ENABLED, DEFAULT_PORT_FORWARDING_ENABLED);
  760.     }
  761.  
  762.     public void setPortForwardingEnabled(boolean isPortForwardingEnabled) {
  763.         setBoolean(KEY_PORT_FORWARDING_ENABLED, isPortForwardingEnabled);
  764.     }
  765.  
  766.     public int getPort() {
  767.         return Integer.valueOf(properties.getProperty(KEY_PORT, String.valueOf(DEFAULT_PORT)));
  768.     }
  769.  
  770.     public void setPort(int port) {
  771.         setProperty(KEY_PORT, String.valueOf(port));
  772.     }
  773.  
  774.     public int getHttpsPort() {
  775.         return Integer.valueOf(properties.getProperty(KEY_HTTPS_PORT, String.valueOf(DEFAULT_HTTPS_PORT)));
  776.     }
  777.  
  778.     public void setHttpsPort(int httpsPort) {
  779.         setProperty(KEY_HTTPS_PORT, String.valueOf(httpsPort));
  780.     }
  781.  
  782.     public boolean isUrlRedirectionEnabled() {
  783.         return getBoolean(KEY_URL_REDIRECTION_ENABLED, DEFAULT_URL_REDIRECTION_ENABLED);
  784.     }
  785.  
  786.     public void setUrlRedirectionEnabled(boolean isUrlRedirectionEnabled) {
  787.         setBoolean(KEY_URL_REDIRECTION_ENABLED, isUrlRedirectionEnabled);
  788.     }
  789.  
  790.     public String getUrlRedirectFrom() {
  791.         return properties.getProperty(KEY_URL_REDIRECT_FROM, DEFAULT_URL_REDIRECT_FROM);
  792.     }
  793.  
  794.     public void setUrlRedirectFrom(String urlRedirectFrom) {
  795.         properties.setProperty(KEY_URL_REDIRECT_FROM, urlRedirectFrom);
  796.     }
  797.  
  798.     public Date getTrialExpires() {
  799.         String value = properties.getProperty(KEY_TRIAL_EXPIRES, DEFAULT_TRIAL_EXPIRES);
  800.         return value == null ? null : new Date(Long.parseLong(value));
  801.     }
  802.  
  803.     private void setTrialExpires(Date date) {
  804.         String value = (date == null ? null : String.valueOf(date.getTime()));
  805.         setProperty(KEY_TRIAL_EXPIRES, value);
  806.     }
  807.  
  808.     public String getUrlRedirectContextPath() {
  809.         return properties.getProperty(KEY_URL_REDIRECT_CONTEXT_PATH, DEFAULT_URL_REDIRECT_CONTEXT_PATH);
  810.     }
  811.  
  812.     public void setUrlRedirectContextPath(String contextPath) {
  813.         properties.setProperty(KEY_URL_REDIRECT_CONTEXT_PATH, contextPath);
  814.     }
  815.  
  816.     public String getServerId() {
  817.         return properties.getProperty(KEY_SERVER_ID, DEFAULT_SERVER_ID);
  818.     }
  819.  
  820.     public void setServerId(String serverId) {
  821.         properties.setProperty(KEY_SERVER_ID, serverId);
  822.     }
  823.  
  824.     public long getSettingsChanged() {
  825.         return Long.parseLong(properties.getProperty(KEY_SETTINGS_CHANGED, String.valueOf(DEFAULT_SETTINGS_CHANGED)));
  826.     }
  827.  
  828.     public Date getLastScanned() {
  829.         String lastScanned = properties.getProperty(KEY_LAST_SCANNED);
  830.         return lastScanned == null ? null : new Date(Long.parseLong(lastScanned));
  831.     }
  832.  
  833.     public void setLastScanned(Date date) {
  834.         if (date == null) {
  835.             properties.remove(KEY_LAST_SCANNED);
  836.         } else {
  837.             properties.setProperty(KEY_LAST_SCANNED, String.valueOf(date.getTime()));
  838.         }
  839.     }
  840.  
  841.     public boolean isOrganizeByFolderStructure() {
  842.         return getBoolean(KEY_ORGANIZE_BY_FOLDER_STRUCTURE, DEFAULT_ORGANIZE_BY_FOLDER_STRUCTURE);
  843.     }
  844.  
  845.     public void setOrganizeByFolderStructure(boolean b) {
  846.         setBoolean(KEY_ORGANIZE_BY_FOLDER_STRUCTURE, b);
  847.     }
  848.  
  849.     public boolean isSortAlbumsByYear() {
  850.         return getBoolean(KEY_SORT_ALBUMS_BY_YEAR, DEFAULT_SORT_ALBUMS_BY_YEAR);
  851.     }
  852.  
  853.     public void setSortAlbumsByYear(boolean b) {
  854.         setBoolean(KEY_SORT_ALBUMS_BY_YEAR, b);
  855.     }
  856.  
  857.     public MediaLibraryStatistics getMediaLibraryStatistics() {
  858.         return MediaLibraryStatistics.parse(getString(KEY_MEDIA_LIBRARY_STATISTICS, DEFAULT_MEDIA_LIBRARY_STATISTICS));
  859.     }
  860.  
  861.     public void setMediaLibraryStatistics(MediaLibraryStatistics statistics) {
  862.         setString(KEY_MEDIA_LIBRARY_STATISTICS, statistics.format());
  863.     }
  864.  
  865.     /**
  866.      * Returns the locale (for language, date format etc).
  867.      *
  868.      * @return The locale.
  869.      */
  870.     public Locale getLocale() {
  871.         String language = properties.getProperty(KEY_LOCALE_LANGUAGE, DEFAULT_LOCALE_LANGUAGE);
  872.         String country = properties.getProperty(KEY_LOCALE_COUNTRY, DEFAULT_LOCALE_COUNTRY);
  873.         String variant = properties.getProperty(KEY_LOCALE_VARIANT, DEFAULT_LOCALE_VARIANT);
  874.  
  875.         return new Locale(language, country, variant);
  876.     }
  877.  
  878.     /**
  879.      * Sets the locale (for language, date format etc.)
  880.      *
  881.      * @param locale The locale.
  882.      */
  883.     public void setLocale(Locale locale) {
  884.         setProperty(KEY_LOCALE_LANGUAGE, locale.getLanguage());
  885.         setProperty(KEY_LOCALE_COUNTRY, locale.getCountry());
  886.         setProperty(KEY_LOCALE_VARIANT, locale.getVariant());
  887.     }
  888.  
  889.     /**
  890.      * Returns the ID of the theme to use.
  891.      *
  892.      * @return The theme ID.
  893.      */
  894.     public String getThemeId() {
  895.         return properties.getProperty(KEY_THEME_ID, DEFAULT_THEME_ID);
  896.     }
  897.  
  898.     /**
  899.      * Sets the ID of the theme to use.
  900.      *
  901.      * @param themeId The theme ID
  902.      */
  903.     public void setThemeId(String themeId) {
  904.         setProperty(KEY_THEME_ID, themeId);
  905.     }
  906.  
  907.     /**
  908.      * Returns a list of available themes.
  909.      *
  910.      * @return A list of available themes.
  911.      */
  912.     public synchronized Theme[] getAvailableThemes() {
  913.         if (themes == null) {
  914.             themes = new ArrayList<Theme>();
  915.             try {
  916.                 InputStream in = SettingsService.class.getResourceAsStream(THEMES_FILE);
  917.                 String[] lines = StringUtil.readLines(in);
  918.                 for (String line : lines) {
  919.                     String[] elements = StringUtil.split(line);
  920.                     if (elements.length == 2) {
  921.                         themes.add(new Theme(elements[0], elements[1]));
  922.                     } else if (elements.length == 3) {
  923.                         themes.add(new Theme(elements[0], elements[1], elements[2]));
  924.                     } else {
  925.                         LOG.warn("Failed to parse theme from line: [" + line + "].");
  926.                     }
  927.                 }
  928.             } catch (IOException x) {
  929.                 LOG.error("Failed to resolve list of themes.", x);
  930.                 themes.add(new Theme("default", "Subsonic default"));
  931.             }
  932.         }
  933.         return themes.toArray(new Theme[themes.size()]);
  934.     }
  935.  
  936.     /**
  937.      * Returns a list of available locales.
  938.      *
  939.      * @return A list of available locales.
  940.      */
  941.     public synchronized Locale[] getAvailableLocales() {
  942.         if (locales == null) {
  943.             locales = new ArrayList<Locale>();
  944.             try {
  945.                 InputStream in = SettingsService.class.getResourceAsStream(LOCALES_FILE);
  946.                 String[] lines = StringUtil.readLines(in);
  947.  
  948.                 for (String line : lines) {
  949.                     locales.add(parseLocale(line));
  950.                 }
  951.  
  952.             } catch (IOException x) {
  953.                 LOG.error("Failed to resolve list of locales.", x);
  954.                 locales.add(Locale.ENGLISH);
  955.             }
  956.         }
  957.         return locales.toArray(new Locale[locales.size()]);
  958.     }
  959.  
  960.     private Locale parseLocale(String line) {
  961.         String[] s = line.split("_");
  962.         String language = s[0];
  963.         String country = "";
  964.         String variant = "";
  965.  
  966.         if (s.length > 1) {
  967.             country = s[1];
  968.         }
  969.         if (s.length > 2) {
  970.             variant = s[2];
  971.         }
  972.         return new Locale(language, country, variant);
  973.     }
  974.  
  975.     /**
  976.      * Returns the "brand" name. Normally, this is just "Subsonic".
  977.      *
  978.      * @return The brand name.
  979.      */
  980.     public String getBrand() {
  981.         return "Subsonic";
  982.     }
  983.  
  984.     /**
  985.      * Returns all music folders. Non-existing and disabled folders are not included.
  986.      *
  987.      * @return Possibly empty list of all music folders.
  988.      */
  989.     public List<MusicFolder> getAllMusicFolders() {
  990.         return getAllMusicFolders(false, false);
  991.     }
  992.  
  993.     /**
  994.      * Returns all music folders.
  995.      *
  996.      * @param includeDisabled Whether to include disabled folders.
  997.      * @param includeNonExisting Whether to include non-existing folders.
  998.      * @return Possibly empty list of all music folders.
  999.      */
  1000.     public List<MusicFolder> getAllMusicFolders(boolean includeDisabled, boolean includeNonExisting) {
  1001.         if (cachedMusicFolders == null) {
  1002.             cachedMusicFolders = musicFolderDao.getAllMusicFolders();
  1003.         }
  1004.        
  1005.         List<MusicFolder> result = new ArrayList<MusicFolder>(cachedMusicFolders.size());
  1006.         for (MusicFolder folder : cachedMusicFolders) {
  1007.             if ((includeDisabled || folder.isEnabled()) && (includeNonExisting || FileUtil.exists(folder.getPath()))) {
  1008.                 result.add(folder);
  1009.             }
  1010.         }
  1011.         return result;
  1012.     }
  1013.  
  1014.     /**
  1015.      * Returns the music folder with the given ID.
  1016.      *
  1017.      * @param id The ID.
  1018.      * @return The music folder with the given ID, or <code>null</code> if not found.
  1019.      */
  1020.     public MusicFolder getMusicFolderById(Integer id) {
  1021.         List<MusicFolder> all = getAllMusicFolders();
  1022.         for (MusicFolder folder : all) {
  1023.             if (id.equals(folder.getId())) {
  1024.                 return folder;
  1025.             }
  1026.         }
  1027.         return null;
  1028.     }
  1029.  
  1030.     /**
  1031.      * Creates a new music folder.
  1032.      *
  1033.      * @param musicFolder The music folder to create.
  1034.      */
  1035.     public void createMusicFolder(MusicFolder musicFolder) {
  1036.         musicFolderDao.createMusicFolder(musicFolder);
  1037.         cachedMusicFolders = null;
  1038.     }
  1039.  
  1040.     /**
  1041.      * Deletes the music folder with the given ID.
  1042.      *
  1043.      * @param id The ID of the music folder to delete.
  1044.      */
  1045.     public void deleteMusicFolder(Integer id) {
  1046.         musicFolderDao.deleteMusicFolder(id);
  1047.         cachedMusicFolders = null;
  1048.     }
  1049.  
  1050.     /**
  1051.      * Updates the given music folder.
  1052.      *
  1053.      * @param musicFolder The music folder to update.
  1054.      */
  1055.     public void updateMusicFolder(MusicFolder musicFolder) {
  1056.         musicFolderDao.updateMusicFolder(musicFolder);
  1057.         cachedMusicFolders = null;
  1058.     }
  1059.  
  1060.     /**
  1061.      * Returns all internet radio stations. Disabled stations are not returned.
  1062.      *
  1063.      * @return Possibly empty list of all internet radio stations.
  1064.      */
  1065.     public List<InternetRadio> getAllInternetRadios() {
  1066.         return getAllInternetRadios(false);
  1067.     }
  1068.  
  1069.     /**
  1070.      * Returns the internet radio station with the given ID.
  1071.      *
  1072.      * @param id The ID.
  1073.      * @return The internet radio station with the given ID, or <code>null</code> if not found.
  1074.      */
  1075.     public InternetRadio getInternetRadioById(Integer id) {
  1076.         for (InternetRadio radio : getAllInternetRadios()) {
  1077.             if (id.equals(radio.getId())) {
  1078.                 return radio;
  1079.             }
  1080.         }
  1081.         return null;
  1082.     }
  1083.  
  1084.     /**
  1085.      * Returns all internet radio stations.
  1086.      *
  1087.      * @param includeAll Whether disabled stations should be included.
  1088.      * @return Possibly empty list of all internet radio stations.
  1089.      */
  1090.     public List<InternetRadio> getAllInternetRadios(boolean includeAll) {
  1091.         List<InternetRadio> all = internetRadioDao.getAllInternetRadios();
  1092.         List<InternetRadio> result = new ArrayList<InternetRadio>(all.size());
  1093.         for (InternetRadio folder : all) {
  1094.             if (includeAll || folder.isEnabled()) {
  1095.                 result.add(folder);
  1096.             }
  1097.         }
  1098.         return result;
  1099.     }
  1100.  
  1101.     /**
  1102.      * Creates a new internet radio station.
  1103.      *
  1104.      * @param radio The internet radio station to create.
  1105.      */
  1106.     public void createInternetRadio(InternetRadio radio) {
  1107.         internetRadioDao.createInternetRadio(radio);
  1108.     }
  1109.  
  1110.     /**
  1111.      * Deletes the internet radio station with the given ID.
  1112.      *
  1113.      * @param id The internet radio station ID.
  1114.      */
  1115.     public void deleteInternetRadio(Integer id) {
  1116.         internetRadioDao.deleteInternetRadio(id);
  1117.     }
  1118.  
  1119.     /**
  1120.      * Updates the given internet radio station.
  1121.      *
  1122.      * @param radio The internet radio station to update.
  1123.      */
  1124.     public void updateInternetRadio(InternetRadio radio) {
  1125.         internetRadioDao.updateInternetRadio(radio);
  1126.     }
  1127.  
  1128.     /**
  1129.      * Returns settings for the given user.
  1130.      *
  1131.      * @param username The username.
  1132.      * @return User-specific settings. Never <code>null</code>.
  1133.      */
  1134.     public UserSettings getUserSettings(String username) {
  1135.         UserSettings settings = userDao.getUserSettings(username);
  1136.         return settings == null ? createDefaultUserSettings(username) : settings;
  1137.     }
  1138.  
  1139.     private UserSettings createDefaultUserSettings(String username) {
  1140.         UserSettings settings = new UserSettings(username);
  1141.         settings.setFinalVersionNotificationEnabled(true);
  1142.         settings.setBetaVersionNotificationEnabled(false);
  1143.         settings.setShowNowPlayingEnabled(true);
  1144.         settings.setShowChatEnabled(true);
  1145.         settings.setPartyModeEnabled(false);
  1146.         settings.setNowPlayingAllowed(true);
  1147.         settings.setLastFmEnabled(false);
  1148.         settings.setLastFmUsername(null);
  1149.         settings.setLastFmPassword(null);
  1150.         settings.setChanged(new Date());
  1151.  
  1152.         UserSettings.Visibility playlist = settings.getPlaylistVisibility();
  1153.         playlist.setCaptionCutoff(35);
  1154.         playlist.setArtistVisible(true);
  1155.         playlist.setAlbumVisible(true);
  1156.         playlist.setYearVisible(true);
  1157.         playlist.setDurationVisible(true);
  1158.         playlist.setBitRateVisible(true);
  1159.         playlist.setFormatVisible(true);
  1160.         playlist.setFileSizeVisible(true);
  1161.  
  1162.         UserSettings.Visibility main = settings.getMainVisibility();
  1163.         main.setCaptionCutoff(35);
  1164.         main.setTrackNumberVisible(true);
  1165.         main.setArtistVisible(true);
  1166.         main.setDurationVisible(true);
  1167.  
  1168.         return settings;
  1169.     }
  1170.  
  1171.     /**
  1172.      * Updates settings for the given username.
  1173.      *
  1174.      * @param settings The user-specific settings.
  1175.      */
  1176.     public void updateUserSettings(UserSettings settings) {
  1177.         userDao.updateUserSettings(settings);
  1178.     }
  1179.  
  1180.     /**
  1181.      * Returns all system avatars.
  1182.      *
  1183.      * @return All system avatars.
  1184.      */
  1185.     public List<Avatar> getAllSystemAvatars() {
  1186.         return avatarDao.getAllSystemAvatars();
  1187.     }
  1188.  
  1189.     /**
  1190.      * Returns the system avatar with the given ID.
  1191.      *
  1192.      * @param id The system avatar ID.
  1193.      * @return The avatar or <code>null</code> if not found.
  1194.      */
  1195.     public Avatar getSystemAvatar(int id) {
  1196.         return avatarDao.getSystemAvatar(id);
  1197.     }
  1198.  
  1199.     /**
  1200.      * Returns the custom avatar for the given user.
  1201.      *
  1202.      * @param username The username.
  1203.      * @return The avatar or <code>null</code> if not found.
  1204.      */
  1205.     public Avatar getCustomAvatar(String username) {
  1206.         return avatarDao.getCustomAvatar(username);
  1207.     }
  1208.  
  1209.     /**
  1210.      * Sets the custom avatar for the given user.
  1211.      *
  1212.      * @param avatar   The avatar, or <code>null</code> to remove the avatar.
  1213.      * @param username The username.
  1214.      */
  1215.     public void setCustomAvatar(Avatar avatar, String username) {
  1216.         avatarDao.setCustomAvatar(avatar, username);
  1217.     }
  1218.  
  1219.     public boolean isDlnaEnabled() {
  1220.         return getBoolean(KEY_DLNA_ENABLED, DEFAULT_DLNA_ENABLED);
  1221.     }
  1222.  
  1223.     public void setDlnaEnabled(boolean dlnaEnabled) {
  1224.         setBoolean(KEY_DLNA_ENABLED, dlnaEnabled);
  1225.     }
  1226.  
  1227.     private void setProperty(String key, String value) {
  1228.         if (value == null) {
  1229.             properties.remove(key);
  1230.         } else {
  1231.             properties.setProperty(key, value);
  1232.         }
  1233.     }
  1234.  
  1235.     private String[] toStringArray(String s) {
  1236.         List<String> result = new ArrayList<String>();
  1237.         StringTokenizer tokenizer = new StringTokenizer(s, " ");
  1238.         while (tokenizer.hasMoreTokens()) {
  1239.             result.add(tokenizer.nextToken());
  1240.         }
  1241.  
  1242.         return result.toArray(new String[result.size()]);
  1243.     }
  1244.  
  1245.     private void validateLicense() {
  1246.         String email = getLicenseEmail();
  1247.         Date date = getLicenseDate();
  1248.  
  1249.         if (email == null || date == null) {
  1250.             licenseValidated = false;
  1251.             return;
  1252.         }
  1253.  
  1254.         licenseValidated = true;
  1255.  
  1256.         HttpClient client = new DefaultHttpClient();
  1257.         HttpConnectionParams.setConnectionTimeout(client.getParams(), 120000);
  1258.         HttpConnectionParams.setSoTimeout(client.getParams(), 120000);
  1259.         HttpGet method = new HttpGet("http://subsonic.org/backend/validateLicense.view" + "?email=" + StringUtil.urlEncode(email) +
  1260.                 "&date=" + date.getTime() + "&version=" + versionService.getLocalVersion());
  1261.         try {
  1262.             ResponseHandler<String> responseHandler = new BasicResponseHandler();
  1263.             String content = client.execute(method, responseHandler);
  1264.             licenseValidated = content != null && content.contains("true");
  1265.             if (!licenseValidated) {
  1266.                 LOG.warn("License key is not valid.");
  1267.             }
  1268.             String[] lines = StringUtils.split(content);
  1269.             if (lines.length > 1) {
  1270.                 licenseExpires = new Date(Long.parseLong(lines[1]));
  1271.             }
  1272.  
  1273.         } catch (Throwable x) {
  1274.             LOG.warn("Failed to validate license.", x);
  1275.         } finally {
  1276.             client.getConnectionManager().shutdown();
  1277.         }
  1278.     }
  1279.  
  1280.     public synchronized void scheduleLicenseValidation() {
  1281.         if (licenseValidationFuture != null) {
  1282.             licenseValidationFuture.cancel(true);
  1283.         }
  1284.         Runnable task = new Runnable() {
  1285.             public void run() {
  1286.                 validateLicense();
  1287.             }
  1288.         };
  1289.         licenseValidationFuture = executor.scheduleWithFixedDelay(task, 0L, LICENSE_VALIDATION_DELAY_HOURS, TimeUnit.HOURS);
  1290.     }
  1291.  
  1292.     public void setInternetRadioDao(InternetRadioDao internetRadioDao) {
  1293.         this.internetRadioDao = internetRadioDao;
  1294.     }
  1295.  
  1296.     public void setMusicFolderDao(MusicFolderDao musicFolderDao) {
  1297.         this.musicFolderDao = musicFolderDao;
  1298.     }
  1299.  
  1300.     public void setUserDao(UserDao userDao) {
  1301.         this.userDao = userDao;
  1302.     }
  1303.  
  1304.     public void setAvatarDao(AvatarDao avatarDao) {
  1305.         this.avatarDao = avatarDao;
  1306.     }
  1307.  
  1308.     public void setVersionService(VersionService versionService) {
  1309.         this.versionService = versionService;
  1310.     }
  1311. }