/* * Copyright (c) 2014, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.debug.core.pubserve; import com.google.dart.engine.sdk.DirectoryBasedDartSdk; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.MessageConsole; import com.google.dart.tools.core.model.DartSdkManager; import com.google.dart.tools.core.utilities.net.NetUtils; import com.google.dart.tools.debug.core.DartDebugCorePlugin; import com.google.dart.tools.debug.core.pubserve.PubConnection.PubConnectionListener; import org.eclipse.core.resources.IContainer; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.osgi.util.NLS; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Represents a pub serve process */ public class PubServe { private class ServeDirectoryCallback implements PubCallback<String> { private CountDownLatch latch; private String[] message; public ServeDirectoryCallback(CountDownLatch latch, String[] message) { this.latch = latch; this.message = message; } @Override public void handleResult(PubResult<String> result) { if (result.isError()) { message[0] = result.getErrorMessage(); } else { DartCore.getConsole().println("Serving from " + result.getResult()); } latch.countDown(); } } private static final String SERVE_COMMAND = "serve"; private static final String LOCAL_HOST_ADDR = "localhost"; private static final String WEBSOCKET_URL = "ws://{0}:{1}/"; private static final String PUB_SNAPSHOT_PATH = "bin/snapshots/pub.dart.snapshot"; private Process process = null; private StringBuilder stdOut; private StringBuilder stdError; private MessageConsole console; private PubConnection pubConnection; private String portNumber; private IContainer workingDir; private List<String> servedDirs = new ArrayList<String>(); /** * Starts a pub serve process with the given working directory and directory to be served and * connects to the pub admin server. * * @param workingDir - working directory for the pub serve process * @param arguments - a list of user arguments for pub serve * @param dirToServeName - name of the directory to be served * @throws Exception */ PubServe(IContainer workingDir, String directoryToServe, String[] arguments) throws Exception { console = DartCore.getConsole(); this.workingDir = workingDir; runPubServe(directoryToServe, arguments); connectToPub(); } void dispose() { // TODO(keertip): stop pub serve first when api available if (pubConnection != null) { try { pubConnection.close(); } catch (IOException e) { DartDebugCorePlugin.logError(e); } finally { pubConnection = null; } } if (process != null) { try { process.destroy(); } catch (Exception e) { } finally { process = null; } } } String getPortNumber() { return portNumber; } String getStdErrorString() { return stdError.toString(); } IContainer getWorkingDir() { return workingDir; } boolean isAlive() { return process != null && !isTerminated() && pubConnection != null && pubConnection.isConnected(); } /** * Sends an exit on close command to pub, indicating pub serve should shut down when connection is * lost. * * @throws IOException */ void sendExitOnCloseCommand() throws IOException { PubCommands command = pubConnection.getCommands(); command.exitOnClose(); } /** * Send a urlToAssetId command to the current pub serve * * @param url * @param callback * @throws IOException */ void sendGetAssetIdCommand(String url, PubCallback<PubAsset> callback) throws IOException { PubCommands command = pubConnection.getCommands(); command.urlToAssetId(url, callback); } /** * Send a pathToUrl command to the current pub serve * * @param path * @param callback * @return * @throws IOException */ void sendGetUrlCommand(String path, PubCallback<String> callback) throws IOException { PubCommands command = pubConnection.getCommands(); command.pathToUrl(path, callback); } /** * Send a serve directory command to the current pub serve * * @throws IOException */ void serveDirectory(String dirName) throws Exception { if (!servedDirs.contains(dirName)) { CountDownLatch latch = new CountDownLatch(1); String[] message = new String[1]; message[0] = ""; pubConnection.getCommands().serveDirectory( dirName, new ServeDirectoryCallback(latch, message)); try { latch.await(5000, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { // do nothing } if (message[0].isEmpty()) { servedDirs.add(dirName); } else { throw new CoreException(new Status( IStatus.ERROR, DartDebugCorePlugin.PLUGIN_ID, "Could not serve directory\n" + message[0])); } } } private List<String> buildPubServeCommand(String directoryToServe, String[] args) { DirectoryBasedDartSdk sdk = DartSdkManager.getManager().getSdk(); File pubFile = sdk.getPubExecutable(); List<String> command = new ArrayList<String>(); List<String> pubServeArgs = Arrays.asList(args); pubFile = new File(sdk.getDirectory().getAbsolutePath(), PUB_SNAPSHOT_PATH); command.add(sdk.getVmExecutable().getAbsolutePath()); command.add(pubFile.getAbsolutePath()); command.add(SERVE_COMMAND); command.add(directoryToServe); int pubport; if (!pubServeArgs.contains("--port")) { pubport = NetUtils.getUnusedPort(8080, 8100); if (pubport != -1) { command.add("--port"); command.add(Integer.toString(pubport)); } } command.add("--admin-port"); portNumber = Integer.toString(NetUtils.findUnusedPort(0)); command.add(portNumber); if (!pubServeArgs.contains("--hostname")) { command.add("--hostname"); command.add(LOCAL_HOST_ADDR); } command.addAll(pubServeArgs); return command; } /** * This starts a websocket connection with pub */ private void connectToPub() throws Exception { pubConnection = new PubConnection(new URI(NLS.bind(WEBSOCKET_URL, LOCAL_HOST_ADDR, portNumber))); pubConnection.addConnectionListener(new PubConnectionListener() { @Override public void connectionClosed(PubConnection connection) { pubConnection = null; } }); pubConnection.connect(); sendExitOnCloseCommand(); } private void copyStream(InputStream in, StringBuilder stringBuilder, boolean toConsole) { byte[] buffer = new byte[2048]; try { int count = in.read(buffer); while (count != -1) { if (count > 0) { String str = new String(buffer, 0, count); stringBuilder.append(str); if (toConsole) { console.print(str); } } count = in.read(buffer); } in.close(); } catch (IOException ioe) { DartCore.logInformation("Exception when reading from pub serve process stream", ioe); } } private boolean isTerminated() { try { if (process != null) { process.exitValue(); } } catch (IllegalThreadStateException exception) { return false; } return true; } private boolean runPubServe(String directoryToServe, String[] arguments) { stdOut = new StringBuilder(); stdError = new StringBuilder(); List<String> args = buildPubServeCommand(directoryToServe, arguments); ProcessBuilder builder = new ProcessBuilder(); builder.command(args); builder.directory(workingDir.getLocation().toFile()); try { process = builder.start(); } catch (IOException e) { DartCore.logError(e); return false; } Thread stdoutThread = new Thread(new Runnable() { @Override public void run() { copyStream(process.getInputStream(), stdOut, true); } }); stdoutThread.start(); Thread stderrThread = new Thread(new Runnable() { @Override public void run() { copyStream(process.getErrorStream(), stdError, true); } }); stderrThread.start(); // TODO(keertip): maybe use http:// expr instead? while (!isTerminated() && !stdOut.toString().contains("http://localhost")) { try { Thread.sleep(200); } catch (Exception exception) { } } if (isTerminated()) { return false; } servedDirs.add(directoryToServe); return true; } }