Package net.pms.io

Source Code of net.pms.io.ProcessWrapperImpl

/*
* 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.io;

import net.pms.PMS;
import net.pms.encoders.AviDemuxerInputStream;
import net.pms.util.ProcessUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jna.Platform;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class ProcessWrapperImpl extends Thread implements ProcessWrapper {
  private static final Logger logger = LoggerFactory.getLogger(ProcessWrapperImpl.class);

  /** FONTCONFIG_PATH environment variable name */
  private static final String FONTCONFIG_PATH = "FONTCONFIG_PATH";

  private String cmdLine;
  private Process process;
  private OutputConsumer stdoutConsumer;
  private OutputConsumer stderrConsumer;
  private OutputParams params;
  private boolean destroyed;
  private String[] cmdArray;
  private boolean nullable;
  private ArrayList<ProcessWrapper> attachedProcesses;
  private BufferedOutputFile bo = null;
  private boolean keepStdout;
  private boolean keepStderr;
  private static int processCounter = 0;
  private boolean success;

  @Override
  public String toString() {
    return super.getName();
  }

  public boolean isSuccess() {
    return success;
  }

  public ProcessWrapperImpl(String cmdArray[], OutputParams params) {
    this(cmdArray, params, false, false);
  }

  public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepOutput) {
    this(cmdArray, params, keepOutput, keepOutput);
  }

  public ProcessWrapperImpl(String cmdArray[], OutputParams params, boolean keepStdout, boolean keepStderr) {
    super();

    // Determine a suitable thread name for this process:
    // use the command name, but remove its path first.
    String threadName = cmdArray[0];

    if (threadName.indexOf("/") >= 0) {
      threadName = threadName.substring(threadName.lastIndexOf("/") + 1);
    }

    if (threadName.indexOf("\\") >= 0) {
      threadName = threadName.substring(threadName.lastIndexOf("\\") + 1);
    }

    setName(threadName + "-" + getProcessCounter());

    File exec = new File(cmdArray[0]);

    if (exec.isFile()) {
      cmdArray[0] = exec.getAbsolutePath();
    }

    this.cmdArray = cmdArray;
    StringBuilder sb = new StringBuilder("");

    for (int i = 0; i < cmdArray.length; i++) {
      if (i > 0) {
        sb.append(" ");
      }

      if (cmdArray[i] != null && cmdArray[i].indexOf(" ") >= 0) {
        sb.append("\"" + cmdArray[i] + "\"");
      } else {
        sb.append(cmdArray[i]);
      }
    }

    cmdLine = sb.toString();
    this.params = params;
    this.keepStdout = keepStdout;
    this.keepStderr = keepStderr;
    attachedProcesses = new ArrayList<ProcessWrapper>();
  }

  private synchronized int getProcessCounter() {
    return processCounter++;
  }

  public void attachProcess(ProcessWrapper process) {
    attachedProcesses.add(process);
  }

  public void run() {
    ProcessBuilder pb = new ProcessBuilder(cmdArray);

    try {
      logger.debug("Starting " + cmdLine);

      if (params.workDir != null && params.workDir.isDirectory()) {
        pb.directory(params.workDir);
      }

      // Retrieve all environment variables of the process
      Map<String,String> environment = pb.environment();

      // The variable params.env is initialized to null in the OutputParams
      // constructor and never set to another value in PMS code. Plugins
      // might use it?
      if (params.env != null && !params.env.isEmpty()) {
        // actual name of system path var is case-sensitive
        String sysPathKey = Platform.isWindows() ? "Path" : "PATH";
        // as is Map
        String PATH = params.env.containsKey("PATH") ? params.env.get("PATH") :
          params.env.containsKey("path") ? params.env.get("path") :
          params.env.containsKey("Path") ? params.env.get("Path") : null;

        if (PATH != null) {
          PATH += (File.pathSeparator + environment.get(sysPathKey));
        }

        environment.putAll(params.env);

        if (PATH != null) {
          environment.put(sysPathKey, PATH);
        }
      }

      // Fontconfig on Mac OS X may have problems locating fonts. As a result
      // subtitles may be rendered invisible. Force feed fontconfig the
      // FONTCONFIG_PATH environment variable to the prepackaged fontconfig
      // configuration directory that comes with PMS on Mac OS X to make
      // sure it has sensible defaults.
      if (Platform.isMac()) {
        // Do not overwrite the variable if it already exists.
        if (!environment.containsKey(FONTCONFIG_PATH)) {
          String pmsWorkingDirectory = new File("").getAbsolutePath();
          String fontconfigFontsPath = pmsWorkingDirectory + "/fonts";
          logger.trace("Setting FONTCONFIG_PATH to \"" + fontconfigFontsPath + "\"");
          environment.put(FONTCONFIG_PATH, fontconfigFontsPath);
        }
      }

      // XXX A cleaner way to execute short-running commands (e.g. vlc -version)
      // is being developed. When that's done, this class can be used solely
      // for the long-running tasks i.e. transcodes. At that point, we won't need
      // separate stdout and stderr and can merge them by uncommenting the
      // following line:
      // pb.redirectErrorStream(true);
      process = pb.start();
      PMS.get().currentProcesses.add(process);

      stderrConsumer = keepStderr
        ? new OutputTextConsumer(process.getErrorStream(), true)
        : new OutputTextLogger(process.getErrorStream());
      stderrConsumer.start();
      stdoutConsumer = null;

      if (params.input_pipes[0] != null) {
        logger.debug("Reading pipe: " + params.input_pipes[0].getInputPipe());
        bo = params.input_pipes[0].getDirectBuffer();

        if (bo == null || params.losslessaudio || params.lossyaudio || params.no_videoencode) {
          InputStream is = params.input_pipes[0].getInputStream();

          if (params.avidemux) {
            is = new AviDemuxerInputStream(is, params, attachedProcesses);
          }

          stdoutConsumer = new OutputBufferConsumer(is, params);
          bo = stdoutConsumer.getBuffer();
        }

        bo.attachThread(this);
        new OutputTextLogger(process.getInputStream()).start();
      } else if (params.log) {
        stdoutConsumer = keepStdout
          ? new OutputTextConsumer(process.getInputStream(), true)
          : new OutputTextLogger(process.getInputStream());
      } else {
        stdoutConsumer = new OutputBufferConsumer(process.getInputStream(), params);
        bo = stdoutConsumer.getBuffer();
        bo.attachThread(this);
      }

      if (stdoutConsumer != null) {
        stdoutConsumer.start();
      }

      if (params.stdin != null) {
        params.stdin.push(process.getOutputStream());
      }

      Integer pid = ProcessUtil.getProcessID(process);

      if (pid != null) {
        logger.debug("Unix process ID ({}): {}", cmdArray[0], pid);
      }

      ProcessUtil.waitFor(process);

      // wait up to a second for the stderr consumer thread to finish
      try {
        if (stderrConsumer != null) {
          stderrConsumer.join(1000);
        }
      } catch (InterruptedException e) { }

      // wait up to a second for the stdout consumer thread to finish
      try {
        if (stdoutConsumer != null) {
          stdoutConsumer.join(1000);
        }
      } catch (InterruptedException e) { }
    } catch (Exception e) {
      logger.error("Error initializing process: ", e);
      stopProcess();
    } finally {
      try {
        if (bo != null) {
          bo.close();
        }
      } catch (IOException ioe) {
        logger.debug("Error closing buffered output file", ioe.getMessage());
      }

      if (!destroyed && !params.noexitcheck) {
        try {
          success = true;
          if (process != null && process.exitValue() != 0) {
            logger.info("Process {} has a return code of {}! Maybe an error occurred... check the log file", cmdArray[0], process.exitValue());
            success = false;
          }
        } catch (IllegalThreadStateException itse) {
          logger.error("Error reading process exit value", itse);
        }
      }

      if (attachedProcesses != null) {
        for (ProcessWrapper pw : attachedProcesses) {
          if (pw != null) {
            pw.stopProcess();
          }
        }
      }

      PMS.get().currentProcesses.remove(process);
    }
  }

  /**
   * Same as {@link #start()}, merely making the intention explicit in the
   * method name.
   * @see #runInSameThread()
   */
  public void runInNewThread() {
    this.start();
  }

  /**
   * Same as {@link #run()}, merely making the intention explicit in the
   * method name.
   * @see #runInNewThread()
   */
  public void runInSameThread() {
    this.run();
  }

  public InputStream getInputStream(long seek) throws IOException {
    if (bo != null) {
      return bo.getInputStream(seek);
    } else if (stdoutConsumer != null && stdoutConsumer.getBuffer() != null) {
      return stdoutConsumer.getBuffer().getInputStream(seek);
    }

    return null;
  }

  public List<String> getOtherResults() {
    if (stdoutConsumer == null) {
      return null;
    }

    try {
      stdoutConsumer.join(1000);
    } catch (InterruptedException e) { }

    return stdoutConsumer.getResults();
  }

  public List<String> getResults() {
    try {
      stderrConsumer.join(1000);
    } catch (InterruptedException e) { }

    return stderrConsumer.getResults();
  }

  public synchronized void stopProcess() {
    if (!destroyed) {
      destroyed = true;
      if (process != null) {
        Integer pid = ProcessUtil.getProcessID(process);

        if (pid != null) {
          logger.debug("Stopping Unix process " + pid + ": " + this);
        } else {
          logger.debug("Stopping process: " + this);
        }

        ProcessUtil.destroy(process);
      }

      if (attachedProcesses != null) {
        for (ProcessWrapper pw : attachedProcesses) {
          if (pw != null) {
            pw.stopProcess();
          }
        }
      }

      if (stdoutConsumer != null && stdoutConsumer.getBuffer() != null) {
        stdoutConsumer.getBuffer().reset();
      }
    }
  }

  public boolean isDestroyed() {
    return destroyed;
  }

  public boolean isReadyToStop() {
    return nullable;
  }

  public void setReadyToStop(boolean nullable) {
    if (nullable != this.nullable) {
      logger.trace("Ready to Stop: " + nullable);
    }

    this.nullable = nullable;
  }
}
TOP

Related Classes of net.pms.io.ProcessWrapperImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.