/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008 A.Brochard
*
* This program is 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;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.BindException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.logging.LogManager;
import javax.swing.JOptionPane;
import net.pms.configuration.Build;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.DLNAMediaDatabase;
import net.pms.dlna.RootFolder;
import net.pms.dlna.virtual.MediaLibrary;
import net.pms.encoders.FFMpegAudio;
import net.pms.encoders.FFMpegDVRMSRemux;
import net.pms.encoders.FFMpegVideo;
import net.pms.encoders.MEncoderAviSynth;
import net.pms.encoders.MEncoderVideo;
import net.pms.encoders.MEncoderWebVideo;
import net.pms.encoders.MPlayerAudio;
import net.pms.encoders.MPlayerWebAudio;
import net.pms.encoders.MPlayerWebVideoDump;
import net.pms.encoders.Player;
import net.pms.encoders.RAWThumbnailer;
import net.pms.encoders.TSMuxerVideo;
import net.pms.encoders.TsMuxerAudio;
import net.pms.encoders.VideoLanAudioStreaming;
import net.pms.encoders.VideoLanVideoStreaming;
import net.pms.external.ExternalFactory;
import net.pms.external.ExternalListener;
import net.pms.formats.DVRMS;
import net.pms.formats.FLAC;
import net.pms.formats.Format;
import net.pms.formats.GIF;
import net.pms.formats.ISO;
import net.pms.formats.JPG;
import net.pms.formats.M4A;
import net.pms.formats.MKV;
import net.pms.formats.MP3;
import net.pms.formats.MPG;
import net.pms.formats.OGG;
import net.pms.formats.PNG;
import net.pms.formats.RAW;
import net.pms.formats.TIF;
import net.pms.formats.WAV;
import net.pms.formats.WEB;
import net.pms.gui.DummyFrame;
import net.pms.gui.IFrame;
import net.pms.io.BasicSystemUtils;
import net.pms.io.MacSystemUtils;
import net.pms.io.OutputParams;
import net.pms.io.OutputTextConsumer;
import net.pms.io.ProcessWrapperImpl;
import net.pms.io.SolarisUtils;
import net.pms.io.SystemUtils;
import net.pms.io.WinUtils;
import net.pms.logging.LoggingConfigFileLoader;
import net.pms.network.HTTPServer;
import net.pms.network.ProxyServer;
import net.pms.network.UPNPHelper;
import net.pms.newgui.GeneralTab;
import net.pms.newgui.LooksFrame;
import net.pms.newgui.ProfileChooser;
import net.pms.update.AutoUpdater;
import net.pms.util.ProcessUtil;
import net.pms.util.PropertiesUtil;
import net.pms.util.SystemErrWrapper;
import net.pms.util.TaskRunner;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Platform;
public class PMS {
private static final String SCROLLBARS = "scrollbars";
private static final String NATIVELOOK = "nativelook";
private static final String CONSOLE = "console";
private static final String NOCONSOLE = "noconsole";
private static final String PROFILES = "profiles";
/**
* @deprecated The version has moved to the resources/project.properties file. Use {@link #getVersion()} instead.
*/
@Deprecated
public static String VERSION;
public static final String AVS_SEPARATOR = "\1";
// (innot): The logger used for all logging.
private static final Logger logger = LoggerFactory.getLogger(PMS.class);
// TODO(tcox): This shouldn't be static
private static PmsConfiguration configuration;
/**Returns a pointer to the main PMS GUI.
* @return {@link IFrame} Main PMS window.
*/
public IFrame getFrame() {
return frame;
}
/**getRootFolder returns the Root Folder for a given renderer. There could be the case
* where a given media renderer needs a different root structure.
* @param renderer {@link RendererConfiguration} is the renderer for which to get the RootFolder structure. If null, then
* the default renderer is used.
* @return {@link RootFolder} The root folder structure for a given renderer
*/
public RootFolder getRootFolder(RendererConfiguration renderer) {
// something to do here for multiple directories views for each renderer
if (renderer == null) {
renderer = RendererConfiguration.getDefaultConf();
}
return renderer.getRootFolder();
}
/**
* Pointer to a running PMS server.
*/
private static PMS instance = null;
/**
* @deprecated This field is not used and will be removed in the future.
*/
public final static SimpleDateFormat sdfDate = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
/**
* @deprecated This field is not used and will be removed in the future.
*/
public final static SimpleDateFormat sdfHour = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
/**
* Array of {@link RendererConfiguration} that have been found by PMS.
*/
private final ArrayList foundRenderers = new ArrayList();
/**Adds a {@link RendererConfiguration} to the list of media renderers found. The list is being used, for
* example, to give the user a graphical representation of the found media renderers.
* @param mediarenderer {@link RendererConfiguration}
*/
public void setRendererfound(RendererConfiguration mediarenderer) {
if (!foundRenderers.contains(mediarenderer) && !mediarenderer.isFDSSDP()) {
foundRenderers.add(mediarenderer);
frame.addRendererIcon(mediarenderer.getRank(), mediarenderer.getRendererName(), mediarenderer.getRendererIcon());
frame.setStatusCode(0, Messages.getString("PMS.18"), "apply-220.png");
}
}
/**
* HTTP server that serves the XML files needed by UPnP server and the media files.
*/
private HTTPServer server;
/**
* User friendly name for the server.
*/
private String serverName;
private ArrayList extensions;
/**
* List of registered {@link Player}s.
*/
private ArrayList players;
private ArrayList allPlayers;
/**
* @return ArrayList of {@link Player}s.
*/
public ArrayList getAllPlayers() {
return allPlayers;
}
private ProxyServer proxyServer;
public ProxyServer getProxy() {
return proxyServer;
}
public ArrayList currentProcesses = new ArrayList();
private PMS() {
}
/**
* {@link IFrame} object that represents PMS GUI.
*/
IFrame frame;
/**
* @see Platform#isWindows()
*/
public boolean isWindows() {
return Platform.isWindows();
}
private int proxy;
/**Interface to Windows specific functions, like Windows Registry. registry is set by {@link #init()}.
* @see WinUtils
*/
private SystemUtils registry;
/**
* @see WinUtils
*/
public SystemUtils getRegistry() {
return registry;
}
/**Executes a new Process and creates a fork that waits for its results.
* TODO:Extend explanation on where this is being used.
* @param name Symbolic name for the process to be launched, only used in the trace log
* @param error (boolean) Set to true if you want PMS to add error messages to the trace pane
* @param workDir (File) optional working directory to run the process in
* @param params (array of Strings) array containing the command to call and its arguments
* @return Returns true if the command exited as expected
* @throws Exception TODO: Check which exceptions to use
*/
private boolean checkProcessExistence(String name, boolean error, File workDir, String... params) throws Exception {
logger.debug("launching: " + params[0]);
try {
ProcessBuilder pb = new ProcessBuilder(params);
if (workDir != null) {
pb.directory(workDir);
}
final Process process = pb.start();
OutputTextConsumer stderrConsumer = new OutputTextConsumer(process.getErrorStream(), false);
stderrConsumer.start();
OutputTextConsumer outConsumer = new OutputTextConsumer(process.getInputStream(), false);
outConsumer.start();
Runnable r = new Runnable() {
public void run() {
ProcessUtil.waitFor(process);
}
};
Thread checkThread = new Thread(r, "PMS Checker");
checkThread.start();
checkThread.join(60000);
checkThread.interrupt();
checkThread = null;
// XXX no longer used
if (params[0].equals("vlc") && stderrConsumer.getResults().get(0).startsWith("VLC")) {
return true;
}
// XXX no longer used
if (params[0].equals("ffmpeg") && stderrConsumer.getResults().get(0).startsWith("FF")) {
return true;
}
int exit = process.exitValue();
if (exit != 0) {
if (error) {
logger.info("[" + exit + "] Cannot launch " + name + " / Check the presence of " + params[0] + " ...");
}
return false;
}
return true;
} catch (Exception e) {
if (error) {
logger.error("Cannot launch " + name + " / Check the presence of " + params[0] + " ...", e);
}
return false;
}
}
/**
* @see System#err
*/
@SuppressWarnings("unused")
private final PrintStream stderr = System.err;
/**Main resource database that supports search capabilities. Also known as media cache.
* @see DLNAMediaDatabase
*/
private DLNAMediaDatabase database;
private void initializeDatabase() {
database = new DLNAMediaDatabase("medias");
database.init(false);
}
/**Used to get the database. Needed in the case of the Xbox 360, that requires a database.
* for its queries.
* @return (DLNAMediaDatabase) a reference to the database instance or null if one isn't defined
* (e.g. if the cache is disabled).
*/
public synchronized DLNAMediaDatabase getDatabase() {
return database;
}
/**Initialisation procedure for PMS.
* @return true if the server has been initialized correctly. false if the server could
* not be set to listen on the UPnP port.
* @throws Exception
*/
private boolean init() throws Exception {
AutoUpdater autoUpdater = null;
// Temporary fix for backwards compatibility
VERSION = getVersion();
if (Build.isUpdatable()) {
String serverURL = Build.getUpdateServerURL();
autoUpdater = new AutoUpdater(serverURL, getVersion());
}
registry = createSystemUtils();
if (System.getProperty(CONSOLE) == null) {
frame = new LooksFrame(autoUpdater, configuration);
} else {
System.out.println("GUI environment not available");
System.out.println("Switching to console mode");
frame = new DummyFrame();
}
configuration.addConfigurationListener(new ConfigurationListener() {
@Override
public void configurationChanged(ConfigurationEvent event) {
if (!event.isBeforeUpdate()) {
if (PmsConfiguration.NEED_RELOAD_FLAGS.contains(event.getPropertyName())) {
frame.setReloadable(true);
}
}
}
});
frame.setStatusCode(0, Messages.getString("PMS.130"), "connect_no-220.png");
proxy = -1;
logger.info("Starting PS3 Media Server " + getVersion());
logger.info("by shagrath / 2008-2012");
logger.info("http://ps3mediaserver.org");
logger.info("http://code.google.com/p/ps3mediaserver");
logger.info("Custom build by SubJunk, http://www.spirton.com");
logger.info("");
logger.info("Java: " + System.getProperty("java.version") + "-" + System.getProperty("java.vendor"));
logger.info("OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version"));
logger.info("Encoding: " + System.getProperty("file.encoding"));
String cwd = new File("").getAbsolutePath();
logger.info("Working directory: " + cwd);
logger.info("Temp folder: " + configuration.getTempFolder());
logger.info("Logging config file: " + LoggingConfigFileLoader.getConfigFilePath());
HashMap lfps = LoggingConfigFileLoader.getLogFilePaths();
if (lfps != null && lfps.size() > 0) {
if (lfps.size() == 1) {
Entry entry = lfps.entrySet().iterator().next();
logger.info(String.format("%s: %s", entry.getKey(), entry.getValue()));
} else {
logger.info("Logging to multiple files:");
Iterator> logsIterator = lfps.entrySet().iterator();
Entry entry;
while (logsIterator.hasNext()) {
entry = logsIterator.next();
logger.info(String.format("%s: %s", entry.getKey(), entry.getValue()));
}
}
}
logger.info("");
logger.info("Profile directory: " + configuration.getProfileDirectory());
String profilePath = configuration.getProfilePath();
logger.info("Profile path: " + profilePath);
File profileFile = new File(profilePath);
if (profileFile.exists()) {
String status = String.format("%s%s",
profileFile.canRead() ? "r" : "-",
profileFile.canWrite() ? "w" : "-"
);
logger.info("Profile status: " + status);
} else {
logger.info("Profile status: no such file");
}
logger.info("Profile name: " + configuration.getProfileName());
logger.info("");
RendererConfiguration.loadRendererConfigurations();
logger.info("Checking MPlayer font cache. It can take a minute or so.");
checkProcessExistence("MPlayer", true, null, configuration.getMplayerPath(), "dummy");
if (isWindows()) {
checkProcessExistence("MPlayer", true, configuration.getTempFolder(), configuration.getMplayerPath(), "dummy");
}
logger.info("Done!");
// check the existence of Vsfilter.dll
if (registry.isAvis() && registry.getAvsPluginsDir() != null) {
logger.info("Found AviSynth plugins dir: " + registry.getAvsPluginsDir().getAbsolutePath());
File vsFilterdll = new File(registry.getAvsPluginsDir(), "VSFilter.dll");
if (!vsFilterdll.exists()) {
logger.info("VSFilter.dll is not in the AviSynth plugins directory. This can cause problems when trying to play subtitled videos with AviSynth");
}
}
if (registry.getVlcv() != null && registry.getVlcp() != null) {
logger.info("Found VideoLAN version " + registry.getVlcv() + " at: " + registry.getVlcp());
}
//check if Kerio is installed
if (registry.isKerioFirewall()) {
//todo: Warning message
}
// force use of specific dvr ms muxer when it's installed in the right place
File dvrsMsffmpegmuxer = new File("win32/dvrms/ffmpeg_MPGMUX.exe");
if (dvrsMsffmpegmuxer.exists()) {
configuration.setFfmpegAlternativePath(dvrsMsffmpegmuxer.getAbsolutePath());
}
// disable jaudiotagger logging
LogManager.getLogManager().readConfiguration(new ByteArrayInputStream("org.jaudiotagger.level=OFF".getBytes()));
// wrap System.err
System.setErr(new PrintStream(new SystemErrWrapper(), true));
extensions = new ArrayList();
players = new ArrayList();
allPlayers = new ArrayList();
server = new HTTPServer(configuration.getServerPort());
registerExtensions();
/*
* XXX: keep this here (i.e. after registerExtensions and before registerPlayers) so that plugins
* can register custom players correctly (e.g. in the GUI) and/or add/replace custom formats
*
* XXX: if a plugin requires initialization/notification even earlier than
* this, then a new external listener implementing a new callback should be added
* e.g. StartupListener.registeredExtensions()
*/
try {
ExternalFactory.lookup();
} catch (Exception e) {
logger.error("Error loading plugins", e);
}
// a static block in Player doesn't work (i.e. is called too late).
// this must always be called *after* the plugins have loaded.
// here's as good a place as any
Player.initializeFinalizeTranscoderArgsListeners();
registerPlayers();
// Instantiate listeners that require registered players.
ExternalFactory.instantiateLateListeners();
boolean binding = false;
try {
binding = server.start();
} catch (BindException b) {
logger.info("FATAL ERROR: Unable to bind on port: " + configuration.getServerPort() + ", because: " + b.getMessage());
logger.info("Maybe another process is running or the hostname is wrong.");
}
new Thread("Connection Checker") {
@Override
public void run() {
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
}
if (foundRenderers.isEmpty()) {
frame.setStatusCode(0, Messages.getString("PMS.0"), "messagebox_critical-220.png");
} else {
frame.setStatusCode(0, Messages.getString("PMS.18"), "apply-220.png");
}
}
}.start();
if (!binding) {
return false;
}
if (proxy > 0) {
logger.info("Starting HTTP Proxy Server on port: " + proxy);
proxyServer = new ProxyServer(proxy);
}
// initialize the cache
if (configuration.getUseCache()) {
initializeDatabase(); // XXX: this must be done *before* new MediaLibrary -> new MediaLibraryFolder
mediaLibrary = new MediaLibrary();
logger.info("A tiny cache admin interface is available at: http://" + server.getHost() + ":" + server.getPort() + "/console/home");
}
// XXX: this must be called:
// a) *after* loading plugins i.e. plugins register root folders then RootFolder.discoverChildren adds them
// b) *after* mediaLibrary is initialized, if enabled (above)
getRootFolder(RendererConfiguration.getDefaultConf());
frame.serverReady();
//UPNPHelper.sendByeBye();
Runtime.getRuntime().addShutdownHook(new Thread("PMS Listeners Stopper") {
@Override
public void run() {
try {
for (ExternalListener l : ExternalFactory.getExternalListeners()) {
l.shutdown();
}
UPNPHelper.shutDownListener();
UPNPHelper.sendByeBye();
logger.debug("Forcing shutdown of all active processes");
for (Process p : currentProcesses) {
try {
p.exitValue();
} catch (IllegalThreadStateException ise) {
logger.trace("Forcing shutdown of process: " + p);
ProcessUtil.destroy(p);
}
}
get().getServer().stop();
Thread.sleep(500);
} catch (Exception e) {
}
}
});
UPNPHelper.sendAlive();
logger.trace("Waiting 250 milliseconds...");
Thread.sleep(250);
UPNPHelper.listen();
return true;
}
private MediaLibrary mediaLibrary;
/**Returns the MediaLibrary used by PMS.
* @return (MediaLibrary) Used mediaLibrary, if any. null if none is in use.
*/
public MediaLibrary getLibrary() {
return mediaLibrary;
}
private SystemUtils createSystemUtils() {
if (Platform.isWindows()) {
return new WinUtils();
} else {
if (Platform.isMac()) {
return new MacSystemUtils();
} else {
if (Platform.isSolaris()) {
return new SolarisUtils();
} else {
return new BasicSystemUtils();
}
}
}
}
/**Executes the needed commands in order to make PMS a Windows service that starts whenever the machine is started.
* This function is called from the Network tab.
* @return true if PMS could be installed as a Windows service.
* @see GeneralTab#build()
*/
public boolean installWin32Service() {
logger.info(Messages.getString("PMS.41"));
String cmdArray[] = new String[]{"win32/service/wrapper.exe", "-r", "wrapper.conf"};
OutputParams output = new OutputParams(configuration);
output.noexitcheck = true;
ProcessWrapperImpl pwuninstall = new ProcessWrapperImpl(cmdArray, output);
pwuninstall.runInSameThread();
cmdArray = new String[]{"win32/service/wrapper.exe", "-i", "wrapper.conf"};
ProcessWrapperImpl pwinstall = new ProcessWrapperImpl(cmdArray, new OutputParams(configuration));
pwinstall.runInSameThread();
return pwinstall.isSuccess();
}
/**Add a known set of extensions to the extensions list.
* @see PMS#init()
*/
private void registerExtensions() {
extensions.add(new WEB());
extensions.add(new MKV());
extensions.add(new M4A());
extensions.add(new MP3());
extensions.add(new ISO());
extensions.add(new MPG());
extensions.add(new WAV());
extensions.add(new JPG());
extensions.add(new OGG());
extensions.add(new PNG());
extensions.add(new GIF());
extensions.add(new TIF());
extensions.add(new FLAC());
extensions.add(new DVRMS());
extensions.add(new RAW());
}
/**Register a known set of audio/video transcoders (known as {@link Player}s). Used in PMS#init().
* @see PMS#init()
*/
private void registerPlayers() {
if (Platform.isWindows()) {
registerPlayer(new FFMpegVideo());
}
registerPlayer(new FFMpegAudio(configuration));
registerPlayer(new MEncoderVideo(configuration));
if (Platform.isWindows()) {
registerPlayer(new MEncoderAviSynth(configuration));
}
registerPlayer(new MPlayerAudio(configuration));
registerPlayer(new MEncoderWebVideo(configuration));
registerPlayer(new MPlayerWebVideoDump(configuration));
registerPlayer(new MPlayerWebAudio(configuration));
registerPlayer(new TSMuxerVideo(configuration));
registerPlayer(new TsMuxerAudio(configuration));
registerPlayer(new VideoLanAudioStreaming(configuration));
registerPlayer(new VideoLanVideoStreaming(configuration));
if (Platform.isWindows()) {
registerPlayer(new FFMpegDVRMSRemux());
}
registerPlayer(new RAWThumbnailer());
frame.addEngines();
}
/**Adds a single {@link Player} to the list of Players. Used by {@link PMS#registerPlayers()}.
* @param p (Player) to be added to the list
* @see Player
* @see PMS#registerPlayers()
*/
public void registerPlayer(Player p) {
allPlayers.add(p);
boolean ok = false;
if (Player.NATIVE.equals(p.executable())) {
ok = true;
} else {
if (isWindows()) {
if (p.executable() == null) {
logger.info("Executable of transcoder profile " + p + " not found");
return;
}
File executable = new File(p.executable());
File executable2 = new File(p.executable() + ".exe");
if (executable.exists() || executable2.exists()) {
ok = true;
} else {
logger.info("Executable of transcoder profile " + p + " not found");
return;
}
if (p.avisynth()) {
ok = false;
if (registry.isAvis()) {
ok = true;
} else {
logger.info("Transcoder profile " + p + " will not be used because AviSynth was not found");
}
}
} else if (!p.avisynth()) {
ok = true;
}
}
if (ok) {
logger.info("Registering transcoding engine: " + p /*+ (p.avisynth()?(" with " + (forceMPlayer?"MPlayer":"AviSynth")):"")*/);
players.add(p);
}
}
/**Transforms a comma separated list of directory entries into an array of {@link String}.
* Checks that the directory exists and is a valid directory.
* @param log whether to output log information
* @return {@link File}[] Array of directories.
* @throws IOException
*/
// this is called *way* too often (e.g. a dozen times with 1 renderer and 1 shared folder),
// so log it by default so we can fix it.
// BUT it's also called when the GUI is initialized (to populate the list of shared folders),
// and we don't want this message to appear *before* the PMS banner, so allow that call to suppress logging
public File[] getFoldersConf(boolean log) {
String folders = getConfiguration().getFolders();
if (folders == null || folders.length() == 0) {
return null;
}
ArrayList directories = new ArrayList();
String[] foldersArray = folders.split(",");
for (String folder : foldersArray) {
// unescape embedded commas. note: backslashing isn't safe as it conflicts with
// Windows path separators:
// http://ps3mediaserver.org/forum/viewtopic.php?f=14&t=8883&start=250#p43520
folder = folder.replaceAll(",", ",");
if (log) {
logger.info("Checking shared folder: " + folder);
}
File file = new File(folder);
if (file.exists()) {
if (!file.isDirectory()) {
logger.warn("The file " + folder + " is not a directory! Please remove it from your Shared folders list on the Navigation/Share Settings tab");
}
} else {
logger.warn("The directory " + folder + " does not exist. Please remove it from your Shared folders list on the Navigation/Share Settings tab");
}
// add the file even if there are problems so that the user can update the shared folders as required.
directories.add(file);
}
File f[] = new File[directories.size()];
directories.toArray(f);
return f;
}
public File[] getFoldersConf() {
return getFoldersConf(true);
}
/**Restarts the server. The trigger is either a button on the main PMS window or via
* an action item.
* @throws IOException
*/
// XXX: don't try to optimize this by reusing the same server instance.
// see the comment above HTTPServer.stop()
public void reset() {
TaskRunner.getInstance().submitNamed("restart", true, new Runnable() {
public void run() {
try {
logger.trace("Waiting 1 second...");
UPNPHelper.sendByeBye();
server.stop();
server = null;
RendererConfiguration.resetAllRenderers();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
server = new HTTPServer(configuration.getServerPort());
server.start();
UPNPHelper.sendAlive();
frame.setReloadable(false);
} catch (IOException e) {
logger.error("error during restart :" +e.getMessage(), e);
}
}
});
}
// Cannot remove these methods because of backwards compatibility;
// none of the PMS code uses it, but some plugins still do.
/**
* @deprecated Use the SLF4J logging API instead.
* Adds a message to the debug stream, or {@link System#out} in case the
* debug stream has not been set up yet.
* @param msg {@link String} to be added to the debug stream.
*/
@Deprecated
public static void debug(String msg) {
logger.trace(msg);
}
/**
* @deprecated Use the SLF4J logging API instead.
* Adds a message to the info stream.
* @param msg {@link String} to be added to the info stream.
*/
@Deprecated
public static void info(String msg) {
logger.debug(msg);
}
/**
* @deprecated Use the SLF4J logging API instead.
* Adds a message to the minimal stream. This stream is also
* shown in the Trace tab.
* @param msg {@link String} to be added to the minimal stream.
*/
@Deprecated
public static void minimal(String msg) {
logger.info(msg);
}
/**
* @deprecated Use the SLF4J logging API instead.
* Adds a message to the error stream. This is usually called by
* statements that are in a try/catch block.
* @param msg {@link String} to be added to the error stream
* @param t {@link Throwable} comes from an {@link Exception}
*/
@Deprecated
public static void error(String msg, Throwable t) {
logger.error(msg, t);
}
/**Universally Unique Identifier used in the UPnP server.
*
*/
private String uuid;
/**Creates a new {@link #uuid} for the UPnP server to use. Tries to follow the RFCs for creating the UUID based on the link MAC address.
* Defaults to a random one if that method is not available.
* @return {@link String} with an Universally Unique Identifier.
*/
public String usn() {
if (uuid == null) {
//retrieve UUID from configuration
uuid = getConfiguration().getUuid();
if (uuid == null) {
//create a new UUID based on the MAC address of the used network adapter
NetworkInterface ni = null;
try {
if (configuration.getServerHostname() != null && configuration.getServerHostname().length() > 0) {
ni = NetworkInterface.getByInetAddress(InetAddress.getByName(configuration.getServerHostname()));
} else if (get().getServer().getNi() != null) {
ni = get().getServer().getNi();
}
if (ni != null) {
byte[] addr = getRegistry().getHardwareAddress(ni); // return null when java.net.preferIPv4Stack=true
if (addr != null) {
uuid = UUID.nameUUIDFromBytes(addr).toString();
logger.info(String.format("Generated new UUID based on the MAC address of the network adapter '%s'", ni.getDisplayName()));
}
}
} catch (Throwable e) {
//do nothing
}
//create random UUID if the generation by MAC address failed
if (uuid == null) {
uuid = UUID.randomUUID().toString();
logger.info("Generated new random UUID");
}
//save the newly generated UUID
getConfiguration().setUuid(uuid);
try {
getConfiguration().save();
} catch (ConfigurationException e) {
logger.error("Failed to save configuration with new UUID", e);
}
}
logger.info("Using the following UUID configured in PMS.conf: " + uuid);
}
return "uuid:" + uuid;
}
/**Returns the user friendly name of the UPnP server.
* @return {@link String} with the user friendly name.
*/
public String getServerName() {
if (serverName == null) {
StringBuilder sb = new StringBuilder();
sb.append(System.getProperty("os.name").replace(" ", "_"));
sb.append("-");
sb.append(System.getProperty("os.arch").replace(" ", "_"));
sb.append("-");
sb.append(System.getProperty("os.version").replace(" ", "_"));
sb.append(", UPnP/1.0, PMS/" + getVersion());
serverName = sb.toString();
}
return serverName;
}
/**Returns the PMS instance.
* @return {@link PMS}
*/
public static PMS get() {
// XXX when PMS is run as an application, the instance is initialized via the createInstance call in main().
// However, plugin tests may need access to a PMS instance without going
// to the trouble of launching the PMS application, so we provide a fallback
// initialization here. Either way, createInstance() should only be called once (see below)
if (instance == null) {
createInstance();
}
return instance;
}
private synchronized static void createInstance() {
assert instance == null; // this should only be called once
instance = new PMS();
try {
if (instance.init()) {
logger.info("The server should now appear on your renderer");
} else {
logger.error("A serious error occurred during PMS init");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @param filename
* @return The format.
*/
public Format getAssociatedExtension(String filename) {
logger.trace("Search extension for " + filename);
for (Format ext : extensions) {
if (ext.match(filename)) {
logger.trace("Found 1! " + ext.getClass().getName());
return ext.duplicate();
}
}
return null;
}
public Player getPlayer(Class extends Player> profileClass, Format ext) {
for (Player p : players) {
if (p.getClass().equals(profileClass) && p.type() == ext.getType() && !p.excludeFormat(ext)) {
return p;
}
}
return null;
}
public ArrayList getPlayers(ArrayList> profileClasses, int type) {
ArrayList compatiblePlayers = new ArrayList();
for (Player p : players) {
if (profileClasses.contains(p.getClass()) && p.type() == type) {
compatiblePlayers.add(p);
}
}
return compatiblePlayers;
}
public static void main(String args[]) throws IOException, ConfigurationException {
boolean displayProfileChooser = false;
boolean headless = true;
if (args.length > 0) {
for (int a = 0; a < args.length; a++) {
if (args[a].equals(CONSOLE)) {
System.setProperty(CONSOLE, Boolean.toString(true));
} else if (args[a].equals(NATIVELOOK)) {
System.setProperty(NATIVELOOK, Boolean.toString(true));
} else if (args[a].equals(SCROLLBARS)) {
System.setProperty(SCROLLBARS, Boolean.toString(true));
} else if (args[a].equals(NOCONSOLE)) {
System.setProperty(NOCONSOLE, Boolean.toString(true));
} else if (args[a].equals(PROFILES)) {
displayProfileChooser = true;
}
}
}
try {
Toolkit.getDefaultToolkit();
if (GraphicsEnvironment.isHeadless()) {
if (System.getProperty(NOCONSOLE) == null) {
System.setProperty(CONSOLE, Boolean.toString(true));
}
} else {
headless = false;
}
} catch (Throwable t) {
System.err.println("Toolkit error: " + t.getMessage());
if (System.getProperty(NOCONSOLE) == null) {
System.setProperty(CONSOLE, Boolean.toString(true));
}
}
if (!headless && displayProfileChooser) {
ProfileChooser.display();
}
try {
configuration = new PmsConfiguration();
assert configuration != null;
// Load the (optional) logback config file. This has to be called after 'new PmsConfiguration'
// as the logging starts immediately and some filters need the PmsConfiguration.
LoggingConfigFileLoader.load();
// create the PMS instance returned by get()
createInstance();
} catch (Throwable t) {
System.err.println("Configuration error: " + t.getMessage());
JOptionPane.showMessageDialog(null, "Configuration error:"+t.getMessage(), "Error initalizing PMS!", JOptionPane.ERROR_MESSAGE);
}
}
public HTTPServer getServer() {
return server;
}
public ArrayList getExtensions() {
return extensions;
}
public ArrayList getPlayers() {
return players;
}
public void save() {
try {
configuration.save();
} catch (ConfigurationException e) {
logger.error("Could not save configuration", e);
}
}
public void storeFileInCache(File file, int formatType) {
if (getConfiguration().getUseCache()) {
if (!getDatabase().isDataExists(file.getAbsolutePath(), file.lastModified())) {
getDatabase().insertData(file.getAbsolutePath(), file.lastModified(), formatType, null);
}
}
}
/**
* Retrieves the {@link net.pms.configuration.PmsConfiguration PmsConfiguration} object
* that contains all configured settings for PMS. The object provides getters for all
* configurable PMS settings.
* @return The configuration object
*/
public static PmsConfiguration getConfiguration() {
return configuration;
}
/**
* Returns the project version for PMS.
*
* @return The project version.
*/
public static String getVersion() {
return PropertiesUtil.getProjectProperties().get("project.version");
}
}