/* * Copyright (c) 2013, 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.tools.core.DartCore; import com.google.dart.tools.core.MessageConsole; import com.google.dart.tools.core.pub.IPubServeListener; import com.google.dart.tools.debug.core.DartLaunchConfigWrapper; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ListenerList; import java.io.IOException; /** * Manages the pub serve process for launches. Clients should call serve(DartLaunchconfigWrapper, * PubCallback<String>) to serve an application and get a url for launch. */ public class PubServeManager implements IResourceChangeListener { private static PubServeManager manager = new PubServeManager(); public static PubServeManager getManager() { return manager; } private MessageConsole console; private PubServe pubserve; private final ListenerList listeners = new ListenerList(); public PubServeManager() { } public void addListener(IPubServeListener listener) { listeners.add(listener); } public void dispose() { if (pubserve != null) { pubserve.dispose(); } ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); } public String getStdErrorString() { return pubserve == null ? "" : pubserve.getStdErrorString(); } /** * Indicates whether pub serve is running */ public boolean isServing() { return pubserve != null && pubserve.isAlive(); } public void notifyListeners(boolean isServing) { for (Object listener : listeners.getListeners()) { ((IPubServeListener) listener).pubServeStatus(isServing); } } public void removeListener(IPubServeListener listener) { listeners.remove(listener); } @Override public void resourceChanged(IResourceChangeEvent event) { if (event.getType() == IResourceChangeEvent.PRE_DELETE) { IResource resource = event.getResource(); if (resource != null && resource instanceof IProject) { IContainer pubServeWorkingDir = getCurrentServeWorkingDir(); if (pubServeWorkingDir != null) { // if project that is being served is going to be deleted, stop pub serve if (pubServeWorkingDir.getProject() == resource) { terminatePubServe(); } } } } else if (event.getDelta() != null && event.getDelta().getKind() == IResourceDelta.CHANGED) { // TODO(keertip): remove when pub can detect changes to pubspec and restart pub serve try { event.getDelta().accept(new IResourceDeltaVisitor() { @Override public boolean visit(IResourceDelta delta) throws CoreException { IResource res = delta.getResource(); if (res == null) { return false; } if (res instanceof IFile) { if (res.getName().equals(DartCore.PUBSPEC_FILE_NAME)) { IContainer pubServeWorkingDir = getCurrentServeWorkingDir(); if (pubServeWorkingDir == res.getParent()) { DartCore.getConsole().printSeparator( "Stopping pub serve, " + res.getFullPath().toString() + " has changed"); terminatePubServe(); } } return false; } return true; } }); } catch (CoreException e) { } } } /** * Sends a get asset for given url command to current pub serve * * @param url * @param callback * @throws IOException */ public void sendGetAssetIdCommand(String url, PubCallback<PubAsset> callback) throws IOException { pubserve.sendGetAssetIdCommand(url, callback); } /** * Sends a getUrl command to the current pub serve * * @param resource * @param callback * @throws IOException */ public void sendGetUrlCommand(IResource resource, PubCallback<String> callback) throws IOException { String path = getPathFromWorkingDir(resource); if (path != null) { if (pubserve.isAlive()) { pubserve.sendGetUrlCommand(path, callback); } } else { DartCore.logInformation("Path from working directory not found for resource " + resource.getName()); } } /** * Starts pub serve for a given launch configuration. Checks if the current pub serve is for the * same pubspec.yaml, if not then starts up pub serve. * * @param wrapper - the launch config wrapper * @param pubConnectionCallback */ public void serve(DartLaunchConfigWrapper wrapper, PubCallback<String> pubConnectionCallback) throws Exception { console = DartCore.getConsole(); IResource resource = wrapper.getApplicationResource(); console.printSeparator("Starting pub serve : " + resource.getProject().getName()); IContainer appDir = DartCore.getApplicationDirectory(resource); if (pubserve != null && pubserve.isAlive() && pubserve.getWorkingDir().equals(appDir)) { // make sure pub is serving the directory, send serve directory command serveDirectory(wrapper.getApplicationResource()); } else { if (pubserve != null) { // terminate existing pub serve if any pubserve.dispose(); } pubserve = new PubServe( appDir, getPubServeRootDir(appDir, resource), wrapper.getPubServeArgumentsAsArray()); ResourcesPlugin.getWorkspace().addResourceChangeListener(this); } sendGetUrlCommand(resource, pubConnectionCallback); notifyListeners(true); } /** * Stop pub serve and notify listeners of change of state. */ public void terminatePubServe() { dispose(); notifyListeners(false); DartCore.getConsole().printSeparator("Pub Serve stopped"); } /** * Returns the working directory / application directory for the current pub serve */ IContainer getCurrentServeWorkingDir() { if (pubserve != null) { return pubserve.getWorkingDir(); } return null; } /** * Returns the name of the directory containing the given resource that can be used as root by pub * serve. Pub serve uses the directories that are siblings to the pubspec as root. * * @param container - directory which contains the pubspec.yaml * @param resource - the resource to launch * @return */ String getPubServeRootDir(IContainer container, IResource resource) { try { IResource[] folders = container.members(); for (IResource folder : folders) { if (folder instanceof IFolder && !(folder.getName().equals(DartCore.PACKAGES_DIRECTORY_NAME) || folder.getName().equals( DartCore.BUILD_DIRECTORY_NAME))) { if (resource.getFullPath().toString().startsWith(folder.getFullPath().toString())) { return folder.getName(); } } } } catch (CoreException e) { DartCore.logInformation("", e); } return null; } /** * Returns the path to the resource from the directory where the pubspec resides. - myproj - * pubspec.yaml - web - index.html => web/index.html */ private String getPathFromWorkingDir(IResource resource) { IContainer workingDir = DartCore.getApplicationDirectory(resource); if (workingDir != null) { return resource.getFullPath().removeFirstSegments(workingDir.getFullPath().segmentCount()).toString(); } return null; } /** * Send a serve directory command to the current pub serve * * @param resource * @throws Exception */ private void serveDirectory(IResource resource) throws Exception { IContainer workingDir = DartCore.getApplicationDirectory(resource); try { pubserve.serveDirectory(getPubServeRootDir(workingDir, resource)); } catch (IOException ioe) { DartCore.logError(ioe); } } }