Java tutorial
/* * Digital Media Server, for streaming digital media to DLNA compatible devices * based on www.ps3mediaserver.org and www.universalmediaserver.com. * Copyright (C) 2016 Digital Media Server developers. * * This program is a free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.configuration; import static org.apache.commons.lang3.StringUtils.isBlank; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConversionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.pms.util.FileUtil; /** * This class adds configurable/custom paths to {@link PlatformProgramPaths}. * * @author Nadahar */ @ThreadSafe public class ConfigurableProgramPaths extends PlatformProgramPaths { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurableProgramPaths.class); /** The {@link Configuration} key for the MPlayer executable type. */ public static final String KEY_MPLAYER_EXECUTABLE_TYPE = "mplayer_executable_type"; /** The {@link Configuration} key for the custom MPlayer path. */ public static final String KEY_MPLAYER_PATH = "mplayer_path"; /** The {@link Configuration} key for the tsMuxeRNew executable type. */ public static final String KEY_TSMUXER_NEW_EXECUTABLE_TYPE = "tsmuxer-new_executable_type"; /** The {@link Configuration} key for the custom tsMuxeRNew path. */ public static final String KEY_TSMUXER_NEW_PATH = "tsmuxer_new_path"; /** The {@link Configuration} key for the FLAC executable type. */ public static final String KEY_FLAC_EXECUTABLE_TYPE = "flac_executable_type"; /** The {@link Configuration} key for the custom FLAC path. */ public static final String KEY_FLAC_PATH = "flac_path"; /** The {@link Configuration} key for the Interframe executable type. */ public static final String KEY_INTERFRAME_EXECUTABLE_TYPE = "interframe_executable_type"; /** The {@link Configuration} key for the custom InterFrame path. */ public static final String KEY_INTERFRAME_PATH = "interframe_path"; private final Configuration configuration; private final PlatformProgramPaths platformPaths = PlatformProgramPaths.get(); /** * Not to be instantiated, get the {@link ExternalProgramInfo} instances * from {@link PmsConfiguration} instead. * * @param configuration the {@link Configuration} to use for custom paths. */ protected ConfigurableProgramPaths(@Nullable Configuration configuration) { this.configuration = configuration; // Read configured paths for all configurable program paths not handled by a Player. setCustomPathFromConfiguration(getMPlayer(), KEY_MPLAYER_PATH); setCustomPathFromConfiguration(getTsMuxeRNew(), KEY_TSMUXER_NEW_PATH); setCustomPathFromConfiguration(getFLAC(), KEY_FLAC_PATH); setCustomPathFromConfiguration(getInterFrame(), KEY_INTERFRAME_PATH); } @Override public FFmpegProgramInfo getFFmpeg() { return platformPaths.getFFmpeg(); } @Override public ExternalProgramInfo getMPlayer() { return platformPaths.getMPlayer(); } @Override public ExternalProgramInfo getVLC() { return platformPaths.getVLC(); } @Override public ExternalProgramInfo getMEncoder() { return platformPaths.getMEncoder(); } @Override public ExternalProgramInfo getTsMuxeR() { return platformPaths.getTsMuxeR(); } @Override public ExternalProgramInfo getTsMuxeRNew() { return platformPaths.getTsMuxeRNew(); } @Override public ExternalProgramInfo getFLAC() { return platformPaths.getFLAC(); } @Override public ExternalProgramInfo getDCRaw() { return platformPaths.getDCRaw(); } @Override public ExternalProgramInfo getInterFrame() { return platformPaths.getInterFrame(); } /** * Sets a new {@link ProgramExecutableType#CUSTOM} {@link Path} for MPlayer * both in {@link #configuration} and the {@link ExternalProgramInfo}. * * @param path the new {@link Path} or {@code null} to clear it. */ public void setCustomMPlayerPath(@Nullable Path path) { setCustomProgramPath(path, platformPaths.getMPlayer(), KEY_MPLAYER_PATH, true); } /** * Sets a new {@link ProgramExecutableType#CUSTOM} {@link Path} for * "tsMuxeR new" both in {@link #configuration} and the * {@link ExternalProgramInfo}. * * @param path the new {@link Path} or {@code null} to clear it. */ public void setCustomTsMuxeRNewPath(@Nullable Path path) { setCustomProgramPath(path, platformPaths.getTsMuxeRNew(), KEY_TSMUXER_NEW_PATH, true); } /** * Sets a new {@link ProgramExecutableType#CUSTOM} {@link Path} for FLAC * both in {@link #configuration} and the {@link ExternalProgramInfo}. * * @param path the new {@link Path} or {@code null} to clear it. */ public void setCustomFlacPath(@Nullable Path path) { setCustomProgramPath(path, platformPaths.getFLAC(), KEY_FLAC_PATH, true); } /** * Sets a new {@link ProgramExecutableType#CUSTOM} {@link Path} for * Interframe both in {@link #configuration} and the * {@link ExternalProgramInfo}. * * @param path the new {@link Path} or {@code null} to clear it. */ public void setCustomInterFramePath(@Nullable Path path) { setCustomProgramPath(path, platformPaths.getInterFrame(), KEY_INTERFRAME_PATH, true); } /** * Returns the configured {@link ProgramExecutableType} for the specified * {@link ExternalProgramInfo} using the specified {@link Configuration} * key. If the {@link Configuration} doesn't contain any value for the * specified key, the default from the {@link ProgramExecutableType} is * used. * * @param programInfo the {@link ExternalProgramInfo} to use for the default * value. * @param configurationKey the {@link Configuration} key to use. * @return The resulting {@link ProgramExecutableType}. */ @Nullable public ProgramExecutableType getConfiguredExecutableType(@Nullable ExternalProgramInfo programInfo, @Nullable String configurationKey) { if (configuration == null) { return null; } return getConfiguredExecutableType(configurationKey, programInfo == null ? null : programInfo.getDefault()); } /** * Returns the configured {@link ProgramExecutableType} from the specified * {@link Configuration} key. If the {@link Configuration} doesn't contain * any value for the specified key, the specified default is used. * * @param configurationKey the {@link Configuration} key to use. * @param defaultExecutableType the default {@link ProgramExecutableType} if * the specified key has no value. * @return The resulting {@link ProgramExecutableType}. */ @Nullable public ProgramExecutableType getConfiguredExecutableType(@Nullable String configurationKey, @Nullable ProgramExecutableType defaultExecutableType) { if (configuration == null || isBlank(configurationKey)) { return null; } return ProgramExecutableType.toProgramExecutableType(configuration.getString(configurationKey), defaultExecutableType); } /** * Sets the {@link ProgramExecutableType#CUSTOM} path for the given * {@link ExternalProgramInfo} to its configured value. * * @param programInfo the {@link ExternalProgramInfo} for which to set * the {@link ProgramExecutableType#CUSTOM} {@link Path}. * @param configurationKey the {@link Configuration} key to read. */ protected void setCustomPathFromConfiguration(@Nullable ExternalProgramInfo programInfo, @Nullable String configurationKey) { if (programInfo == null || configuration == null) { return; } if (isBlank(configurationKey)) { throw new IllegalArgumentException("configurationKey can't be blank"); } Path customPath; try { customPath = getCustomProgramPath(configurationKey); } catch (ConfigurationException e) { customPath = null; } setCustomProgramPath(customPath, programInfo, configurationKey, false); } /** * Gets the configured custom program {@link Path} from the * {@link Configuration} using the specified key. If the specified key has * no value, {@code null} is returned. * * @param configurationKey the {@link Configuration} key to use. * @return The resulting {@link Path} or {@code null}. * @throws ConfigurationException If the configured value can't be parsed as * a valid {@link Path}. */ @Nullable public Path getCustomProgramPath(@Nullable String configurationKey) throws ConfigurationException { if (isBlank(configurationKey) || configuration == null) { return null; } try { String configuredPath = configuration.getString(configurationKey); if (isBlank(configuredPath)) { return null; } return Paths.get(configuredPath); } catch (ConversionException | InvalidPathException e) { throw new ConfigurationException( "Invalid configured custom program path in \"" + configurationKey + "\": " + e.getMessage(), e); } } /** * Sets a new {@link ProgramExecutableType#CUSTOM} {@link Path} in the * {@link Configuration} for the specified key. No change is done to any * {@link ExternalProgramInfo} instance. To set the {@link Path} both in the * {@link Configuration} and in the {@link ExternalProgramInfo} instance in * one operation, use {@link #setCustomProgramPath}. * * @param customPath the new {@link Path} or {@code null} to clear it. * @param configurationKey the {@link Configuration} key under which to * store the {@code path}. * @return {@code true} if a change was made, {@code false} otherwise. */ public boolean setCustomProgramPathConfiguration(@Nullable Path customPath, @Nonnull String configurationKey) { if (isBlank(configurationKey)) { throw new IllegalArgumentException("configurationKey can't be blank"); } if (configuration == null) { return false; } if (customPath == null) { if (configuration.containsKey(configurationKey)) { configuration.clearProperty(configurationKey); return true; } return false; } boolean changed; try { String currentValue = configuration.getString(configurationKey); changed = !customPath.toString().equals(currentValue); } catch (ConversionException e) { changed = true; } if (changed) { configuration.setProperty(configurationKey, customPath.toString()); } return changed; } /** * Sets the {@link ProgramExecutableType#CUSTOM} path for the given * {@link ExternalProgramInfo}. * * @param customPath the custom executable {@link Path}. * @param programInfo the {@link ExternalProgramInfo} for which to set the * {@link ProgramExecutableType#CUSTOM} {@link Path}. * @param configurationKey the {@link Configuration} key under which to * store the {@code path}. Cannot be {@code null} if * {@code setConfiguration} is {@code true}. * @param setConfiguration whether or not {@code customPath} should also be * stored in the current {@link Configuration}. */ protected void setCustomProgramPath(@Nullable Path customPath, @Nullable ExternalProgramInfo programInfo, @Nullable String configurationKey, boolean setConfiguration) { if (programInfo == null || configuration == null) { return; } if (setConfiguration && isBlank(configurationKey)) { throw new IllegalArgumentException("configurationKey can't be blank if setConfiguration is true"); } if (setConfiguration) { setCustomProgramPathConfiguration(customPath, configurationKey); } customPath = resolveCustomProgramPath(customPath); ReentrantReadWriteLock lock = programInfo.getLock(); lock.writeLock().lock(); try { if (customPath == null) { if (programInfo.getExecutableInfo(ProgramExecutableType.CUSTOM) != null) { programInfo.setExecutableInfo(ProgramExecutableType.CUSTOM, null); programInfo.setOriginalDefault(); LOGGER.debug("Cleared custom {} path", programInfo.getName()); } } else { ExecutableInfo executableInfo = programInfo.getExecutableInfo(ProgramExecutableType.CUSTOM); if (executableInfo == null || !customPath.equals(executableInfo.getPath())) { programInfo.setPath(ProgramExecutableType.CUSTOM, customPath); programInfo.setDefault(ProgramExecutableType.CUSTOM); LOGGER.debug("Set custom {} path \"{}\"", programInfo.getName(), customPath); } } } finally { lock.writeLock().unlock(); } } /** * Resolves the path in that the existence of the specified {@link Path} is * verified. It it doesn't exist and the {@link Path} is relative, an * attempt to look it up in the OS {@code PATH}. If a match is found in the * OS {@code PATH}, that {@link Path} is returned. If not, the input * {@link Path} is returned. * * @param customPath the custom executable {@link Path} to resolve. * @return The same instance as {@code customPath} it it exists or a match * isn't found in the OS {@code PATH}, otherwise the {@link Path} to * the matched executable found in the OS {@code PATH}. */ @Nullable public static Path resolveCustomProgramPath(@Nullable Path customPath) { if (customPath == null) { return null; } if (!Files.exists(customPath) && !customPath.isAbsolute()) { Path osPathCustom = FileUtil.findExecutableInOSPath(customPath); if (osPathCustom != null) { customPath = osPathCustom; } } return customPath; } }