src/com/vectrace/MercurialEclipse/commands/HgCommand.java
author Jerome Negre <jerome+hg@jnegre.org>
Sun Apr 06 20:37:11 2008 +0200 (15 months ago)
changeset 253 c890f1558210
parent 23195b3bb68de86
permissions -rw-r--r--
30s default timeout
        1 package com.vectrace.MercurialEclipse.commands;
        2 
        3 import java.io.BufferedInputStream;
        4 import java.io.ByteArrayOutputStream;
        5 import java.io.File;
        6 import java.io.IOException;
        7 import java.io.InputStream;
        8 import java.io.PrintStream;
        9 import java.util.ArrayList;
       10 import java.util.HashMap;
       11 import java.util.List;
       12 import java.util.Map;
       13 
       14 import org.eclipse.core.resources.IContainer;
       15 import org.eclipse.core.resources.IProject;
       16 import org.eclipse.core.resources.IResource;
       17 
       18 import com.vectrace.MercurialEclipse.MercurialEclipsePlugin;
       19 import com.vectrace.MercurialEclipse.exception.HgException;
       20 import com.vectrace.MercurialEclipse.preferences.MercurialPreferenceConstants;
       21 import com.vectrace.MercurialEclipse.team.MercurialUtilities;
       22 
       23 /**
       24  * 
       25  * @author Jerome Negre <jerome+hg@jnegre.org>
       26  */
       27 public class HgCommand {
       28 
       29 	//some OSes (windows...) are limited, use a hard-coded value
       30 	//to degrade gracefully
       31 	//TODO have an OS-dependent value
       32 	public static final int MAX_PARAMS = 120;
       33 	
       34 	private static PrintStream console = new PrintStream(MercurialUtilities.getMercurialConsole().newOutputStream());
       35 	
       36 	protected static class InputStreamConsumer extends Thread {
       37 		private final InputStream stream;
       38 		private byte[] output;
       39 		
       40 		public InputStreamConsumer(InputStream stream) {
       41 			this.stream = new BufferedInputStream(stream);
       42 		}
       43 		
       44 		@Override
       45 		public void run() {
       46 			try {
       47 				int length;
       48 				byte[] buffer = new byte[1024];
       49 				ByteArrayOutputStream output  = new ByteArrayOutputStream();
       50 				while((length = stream.read(buffer)) != -1) {
       51 					output.write(buffer, 0, length);
       52 				}
       53 				stream.close();
       54 				this.output = output.toByteArray();
       55 			} catch (IOException e) {
       56 				// TODO report the error to the caller thread
       57 				MercurialEclipsePlugin.logError(e);
       58 			}
       59 		}
       60 		
       61 		public byte[] getBytes() {
       62 			return output;
       63 		}
       64 		
       65 	}
       66 	
       67 	private final String command;
       68 	private final File workingDir;
       69 	private final boolean escapeFiles;
       70 	private final List<String> options = new ArrayList<String>();
       71 	private final List<String> files = new ArrayList<String>();
       72 	
       73 	protected HgCommand(String command, File workingDir, boolean escapeFiles) {
       74 		this.command = command;
       75 		this.workingDir = workingDir;
       76 		this.escapeFiles = escapeFiles;
       77 	}
       78 	
       79 	protected HgCommand(String command, IContainer container, boolean escapeFiles) {
       80 		this(
       81 				command,
       82 				container.getLocation().toFile(),
       83 				escapeFiles);
       84 	}
       85 
       86 	protected HgCommand(String command, boolean escapeFiles) {
       87 		this(command, (File)null, escapeFiles);
       88 	}
       89 	
       90 	protected String getHgExecutable() {
       91 		return MercurialEclipsePlugin.getDefault()
       92 			.getPreferenceStore()
       93 			.getString(MercurialPreferenceConstants.MERCURIAL_EXECUTABLE);
       94 	}
       95 	
       96 	protected String getDefaultUserName() {
       97 		return MercurialEclipsePlugin.getDefault()
       98 			.getPreferenceStore()
       99 			.getString(MercurialPreferenceConstants.MERCURIAL_USERNAME);
      100 	}
      101 	
      102 	protected List<String> getCommands() {
      103 		ArrayList<String> result = new ArrayList<String>();
      104 		result.add(getHgExecutable());
      105 		result.add(command);
      106 		result.addAll(options);
      107 		if(escapeFiles && !files.isEmpty()) {
      108 			result.add("--");
      109 		}
      110 		result.addAll(files);
      111 		console.println("Command: ("+result.size()+") "+result);
      112 		//TODO check that length <= MAX_PARAMS
      113 		return result;
      114 	}
      115 	
      116 	protected void addOptions(String... options) {
      117 		for(String option: options) {
      118 			this.options.add(option);
      119 		}
      120 	}
      121 	
      122 	protected void addUserName(String user) {
      123 		this.options.add("-u");
      124 		this.options.add(user!=null?user:getDefaultUserName());
      125 	}
      126 	
      127 	protected void addFiles(String... files) {
      128 		for(String file: files) {
      129 			this.files.add(file);
      130 		}
      131 	}
      132 	
      133 	protected void addFiles(IResource... resources) {
      134 		for(IResource resource: resources) {
      135 			this.files.add(resource.getLocation().toOSString());
      136 		}
      137 	}
      138 	
      139 	protected void addFiles(List<? extends IResource> resources) {
      140 		for(IResource resource: resources) {
      141 			this.files.add(resource.getLocation().toOSString());
      142 		}
      143 	}
      144 	
      145 	/* TODO the timeout should be configurable, for instance a remote
      146 	 * pull will likely exceed the 10 seconds limit
      147 	 */
      148 	protected byte[] executeToBytes() throws HgException {
      149 		try {
      150 			long start = System.currentTimeMillis();
      151 			ProcessBuilder builder = new ProcessBuilder(getCommands());
      152 			builder.redirectErrorStream(true); // makes my life easier
      153 			if(workingDir != null) {
      154 				builder.directory(workingDir);
      155 			}
      156 			Process process = builder.start();
      157 			InputStreamConsumer consumer = new InputStreamConsumer(process.getInputStream());
      158 			consumer.start();
      159 			consumer.join(30000); // 30 seconds timeout
      160 			if(!consumer.isAlive()) {
      161 				if(process.waitFor() == 0) {
      162 					console.println("Done in "+(System.currentTimeMillis()-start)+" ms");
      163 					return consumer.getBytes();
      164 				}
      165 				throw new HgException("Process error, return code: "+process.exitValue()+", message: "+new String(consumer.getBytes()));
      166 			}
      167 			process.destroy();
      168 			throw new HgException("Process timeout");
      169 		} catch (IOException e) {
      170 			throw new HgException(e.getMessage(), e);
      171 		} catch (InterruptedException e) {
      172 			throw new HgException(e.getMessage(), e);
      173 		}
      174 	}
      175 	
      176 	protected String executeToString() throws HgException {
      177 		return new String(executeToBytes());
      178 	}
      179 	
      180 	protected static Map<IProject, List<IResource>> groupByProject(List<IResource> resources) {
      181 		Map<IProject, List<IResource>> result = new HashMap<IProject, List<IResource>>();
      182 		for(IResource resource : resources) {
      183 			List<IResource> list = result.get(resource.getProject());
      184 			if(list == null) {
      185 				list = new ArrayList<IResource>();
      186 				result.put(resource.getProject(), list);
      187 			}
      188 			list.add(resource);
      189 		}
      190 		return result;
      191 	}
      192 }