changeset 29:3b0d6002605a

Add patch to output commands names. Contributed by Giulio Franco.
author Mario Torre <neugens.limasoftware@gmail.com>
date Fri, 15 Apr 2011 17:13:03 +0200
parents 65812348191d
children 8046dcb47c56
files .hgignore src/main/java/org/icedrobot/ika/output/DefaultWorkerStatus.java src/main/java/org/icedrobot/ika/output/StatusKeeper.java src/main/java/org/icedrobot/ika/output/StatusListener.java src/main/java/org/icedrobot/ika/output/UnformattedConsoleWriter.java src/main/java/org/icedrobot/ika/output/WorkerStatus.java src/main/java/org/icedrobot/ika/runtime/IkaRuntime.java src/main/java/org/icedrobot/ika/runtime/RuntimeCommand.java src/main/java/org/icedrobot/ika/runtime/scm/GITCommand.java src/main/java/org/icedrobot/ika/runtime/scm/GITRepository.java src/main/java/org/icedrobot/ika/runtime/scm/GuiltCommand.java src/main/java/org/icedrobot/ika/runtime/scm/HGCommand.java src/main/java/org/icedrobot/ika/runtime/scm/HGRepository.java
diffstat 13 files changed, 858 insertions(+), 74 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Mon Apr 11 22:16:19 2011 +0200
+++ b/.hgignore	Fri Apr 15 17:13:03 2011 +0200
@@ -2,4 +2,7 @@
 .classpath
 .settings
 target
-build
\ No newline at end of file
+build
+
+syntax: glob
+*.orig
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/output/DefaultWorkerStatus.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,144 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.output;
+
+/**
+ * Immutable implementation of the {@link WorkerStatus} interface.
+ */
+public final class DefaultWorkerStatus
+        implements WorkerStatus {
+
+    private long amountDone;
+    private long totalAmount;
+    private boolean detailedProgress;
+    private String status;
+    private float speed;
+    private String name;
+
+
+    public DefaultWorkerStatus(String name) {
+        this.name = name;
+        amountDone = 0;
+        totalAmount = 100;
+        detailedProgress = false;
+        status = "";
+        speed = -1;
+    }
+
+    public DefaultWorkerStatus(String name, float percentage, String status) {
+        this(name);
+        amountDone = (long) (percentage * 100);
+        this.status = status;
+    }
+
+    public DefaultWorkerStatus( String name,
+                                float percentage,
+                                String status,
+                                float speed) {
+
+        this(name, percentage, status);
+        this.speed = speed;
+    }
+
+    public DefaultWorkerStatus( String name,
+                                long done,
+                                long total,
+                                String status) {
+        this(name);
+        amountDone = done;
+        totalAmount = total;
+        this.status = status;
+    }
+
+    public DefaultWorkerStatus( String name,
+                                long done,
+                                long total,
+                                String status,
+                                float speed) {
+
+        this(name, done, total, status);
+        this.speed = speed;
+    }
+
+
+    @Override
+    public float getPercentage() {
+        return amountDone / (float) totalAmount;
+    }
+
+    @Override
+    public boolean completed() {
+        return amountDone >= totalAmount;
+    }
+
+    private UnsupportedOperationException unsupportedDetailedProgress()
+            throws UnsupportedOperationException {
+
+        return new UnsupportedOperationException(
+                "Detailed progress information is not available");
+    }
+
+    @Override
+    public long getDoneWork() throws UnsupportedOperationException {
+        if (detailedProgress) {
+            return amountDone;
+        } else {
+            throw unsupportedDetailedProgress();
+        }
+    }
+
+    @Override
+    public long getTargetWork() throws UnsupportedOperationException {
+        if (detailedProgress) {
+            return totalAmount;
+        } else {
+            throw unsupportedDetailedProgress();
+        }
+    }
+
+    @Override
+    public boolean hasDetailedProgress() {
+        return detailedProgress;
+    }
+
+    @Override
+    public String getStatus() {
+        return status;
+    }
+
+    @Override
+    public float getSpeed() throws UnsupportedOperationException {
+        if (speed >= 0) {
+            return speed;
+        } else {
+            throw new UnsupportedOperationException(
+                    "Speed information is not available");
+        }
+    }
+
+    @Override
+    public boolean hasSpeed() {
+        return speed >= 0;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/output/StatusKeeper.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,207 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.output;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Central repository which can gather status information from multiple
+ * working threads.
+ * Working threads can register to the StatusKeeper using
+ * {@link #registerThread}, then they can periodically send status information
+ * updates with the {@link #updateStatus} method.
+ * Data consumers can access progress information through getter methods or
+ * by registering themselves as listeners.
+ */
+public class StatusKeeper {
+
+    private static StatusKeeper instance;          //Singleton instance
+
+
+    private ConcurrentMap<String, WorkerStatus> workerData;
+    private final List<String> insertionOrder;
+    private CopyOnWriteArrayList<StatusListener> listeners;
+
+
+    /**
+     * StatusKeeper is a singleton, because it is a centralized repository.
+     */
+    private StatusKeeper() {
+        workerData = new ConcurrentHashMap<String, WorkerStatus>();
+        insertionOrder = new ArrayList<String>();
+        listeners = new CopyOnWriteArrayList<StatusListener>();
+    }
+
+
+    /**
+     * Obtains the instance of StatusKeeper
+     */
+    public static synchronized StatusKeeper getInstance() {
+        if (instance == null) {
+            instance = new StatusKeeper();
+        }
+
+        return instance;
+    }
+
+
+    /**
+     * Registers a new worker as active.
+     * Each worker is registered with a unique name
+     * @param workerName    Unique name identifying the worker.
+     * @param status        Current status of the process.
+     */
+    public void registerWorker (WorkerStatus status) {
+
+        if (workerData.putIfAbsent(status.getName(), status) != null) {
+            throw new IllegalArgumentException("Worker " + status.getName()
+                                                + " is already registered.");
+        }
+        
+        synchronized (insertionOrder) {
+            insertionOrder.add(status.getName());
+        }
+    }
+
+    /**
+     * Registers a new worker as active, with a starting empty status.
+     * Each worker is registered with a unique name
+     * @param workerName    Unique name identifying the worker.
+     * @param status        Current status of the process.
+     */
+    public void registerWorker (String workerName) {
+
+        registerWorker(new DefaultWorkerStatus(workerName));
+    }
+
+    /**
+     * Deregister a previously registered worker, notifying its termination.
+     * @param workerName    Name the worker was registered with.
+     */
+    public void deregisterWorker (String workerName) {
+        synchronized (insertionOrder) {
+            if (insertionOrder.remove(workerName) == false) {
+                throw new IllegalArgumentException(
+                                "Worker " + workerName + " is not registered.");
+            }
+        }
+
+        workerData.remove(workerName);
+    }
+
+
+    /**
+     * Updates the status associated to a previously registered worker.
+     * @param workerName    Worker whose status is to be updated
+     * @param data          New status of the worker.
+     */
+    public void reportStatus (WorkerStatus data) {
+        if (workerData.replace(data.getName(), data) == null) {
+            throw new IllegalArgumentException("Worker " + data.getName()
+                                                + " is not registered.");
+        }
+        fireStatusUpdate(data.getName(), data);
+    }
+
+
+    /**
+     * Returns an iterable collection over the workers currently registered and
+     * their statuses.
+     * @return Iterable collection of map entries
+     */
+    public Iterable<WorkerStatus> statusList() {
+        String[] workerNames;
+        ArrayList<WorkerStatus> iterable;
+        WorkerStatus status;
+        
+        synchronized (insertionOrder) {
+            workerNames = new String[insertionOrder.size()];
+            workerNames = insertionOrder.toArray(workerNames);
+        }
+
+        iterable = new ArrayList<WorkerStatus>(workerNames.length);
+        for (String w : workerNames) {
+            status = workerData.get(w);
+            if (status != null) {
+                iterable.add(status);
+            }
+        }
+
+        return iterable;
+    }
+
+    /**
+     * Returns the current status of a worker
+     * @param workerName    Name of the worker
+     * @return              Current status of the worker,
+     *                      or null if workerName is not a registered worker.
+     */
+    public WorkerStatus getWorkerStatus(String workerName) {
+        return workerData.get(workerName);
+    }
+
+    /**
+     * Returns the number of active workers,
+     * i.e. the number of registered workers that are not completed.
+     * @return  Count of active workers
+     */
+    public int getActiveTasksCount() {
+        int aCount = 0;
+
+        for (WorkerStatus stat : workerData.values()) {
+            if (!stat.completed()) {
+                aCount += 1;
+            }
+        }
+
+        return aCount;
+    }
+
+
+    /**
+     * Adds a listener to the list of those that will be notified about
+     * changes in registered tasks' statuses.
+     * @param l     Listener to register
+     */
+    public void addStatusListener (StatusListener l) {
+        listeners.add(l);
+    }
+
+    /**
+     * Removes a status listener from the StatusKeeper
+     * @param l     Listener to remove
+     */
+    public void removeStatusListener (StatusListener l) {
+        listeners.remove(l);
+    }
+
+    public int getStatusListenersCount () {
+        return listeners.size();
+    }
+
+    protected void fireStatusUpdate (String workerName, WorkerStatus status) {
+        for (StatusListener l : listeners) {
+            l.statusUpdate(workerName, status);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/output/StatusListener.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,28 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.output;
+
+import java.util.EventListener;
+
+
+public interface StatusListener
+        extends EventListener {
+    
+    public void statusUpdate(String workerName, WorkerStatus status);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/output/UnformattedConsoleWriter.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,40 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.output;
+
+import java.io.PrintStream;
+
+public class UnformattedConsoleWriter
+        implements StatusListener {
+
+    private PrintStream out;
+
+    public UnformattedConsoleWriter (PrintStream outStream) {
+        out = outStream;
+    }
+
+    @Override
+    public void statusUpdate(String workerName, WorkerStatus status) {
+        out.format( "%s: %s%s",
+                    workerName,
+                    status.getStatus(),
+                    System.getProperty("line.separator"));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/output/WorkerStatus.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,94 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.output;
+
+/**
+ * Client interface for workers that want to notify their status
+ * to the StatusKeeper.
+ */
+public interface WorkerStatus {
+
+    /**
+     * Retrieves the name of the worker this status is related to.
+     * @return Name of the worker
+     */
+    public String getName();
+
+    /**
+     * Retrieves the completion percentage for the task.
+     * @return Completion percentage.
+     */
+    public float getPercentage();
+
+    /**
+     * Checks if the task has been completed.
+     * @return true, if the task is completed.
+     */
+    public boolean completed();
+
+
+    /**
+     * Retrieves the amount of work that has already been performed.
+     * @return Units of performed work.
+     * @throws UnsupportedOperationException if this datum is not available.
+     *          If {@link #hasDetailedProgress()} returs true, this exception
+     *          shall never be thrown.
+     */
+    public long getDoneWork() throws UnsupportedOperationException;
+
+    /**
+     * Retrieves the amount of work the worker was supposed to accomplish.
+     * @return Total amount of work the worker is to perform.
+     * @throws UnsupportedOperationException if this datum is not available.
+     *          If {@link #hasDetailedProgress()} returs true, this exception
+     *          shall never be thrown.
+     */
+    public long getTargetWork() throws UnsupportedOperationException;
+
+    /**
+     * Checks if this status report has detailed information about work units.
+     * @return true, if {@link #getDoneWork()} and {@link #getTargetWork()}
+     *          return a value. false, if they are unsupported.
+     */
+    public boolean hasDetailedProgress();
+
+
+    /**
+     * Returns textual information about the status of the task
+     * @return status of the task
+     */
+    public String getStatus();
+
+
+    /**
+     * Gets the current extimated processing speed of the task (in KiB/s).
+     * @return Processing speed
+     * @throws UnsupportedOperationException if this datum is not available.
+     *          If {@link #hasSpeed()} returs true, this exception
+     *          shall never be thrown.
+     */
+    public float getSpeed() throws UnsupportedOperationException;
+
+    /**
+     * Checks if details about processing speed are present.
+     * @return true, if {@link #getSpeed()} returns a value;
+     *          false if it is unsupported.
+     */
+    public boolean hasSpeed();
+}
--- a/src/main/java/org/icedrobot/ika/runtime/IkaRuntime.java	Mon Apr 11 22:16:19 2011 +0200
+++ b/src/main/java/org/icedrobot/ika/runtime/IkaRuntime.java	Fri Apr 15 17:13:03 2011 +0200
@@ -20,42 +20,132 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.Arrays;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import org.icedrobot.ika.output.DefaultWorkerStatus;
+import org.icedrobot.ika.output.StatusKeeper;
+import org.icedrobot.ika.output.StatusListener;
+import org.icedrobot.ika.output.UnformattedConsoleWriter;
+import org.icedrobot.ika.output.WorkerStatus;
 import org.icedrobot.ika.plugins.IkaPluginException;
 
 public class IkaRuntime {
 
+    private static StatusListener viewer = null;
+
+
+    /**
+     * Executes the given command on the passed path.
+     */
+    public static void exec(File path, RuntimeCommand command) {
+        Process p = startProcess(path, command.getCommandLine());
+        trackProcess(command, p);
+    }
+
     /**
      * Executes the given command on the passed path.
      */
-    public static void exec(File path, String... command) {
+    public static void exec(File path, String... commandLine) {
+        String commandName = "";
+
+        switch (commandLine.length) {
+            default:
+                //Falls through
+            case 2:
+                commandName = commandLine[1] + commandName;
+                //Falls through
+            case 1:
+                commandName = commandLine[0] + commandName;
+                break;
+            case 0:
+                break;
+        }
+        
+        RuntimeCommand command = new RuntimeCommand(commandName, commandLine);
+        exec(path, command);
+    }
+
+    private static Process startProcess(File path, String... commandLine) {
         try {
-//            System.err.println("command: " + Arrays.toString(command) +
-//                               ", path: " + path);
+            Logger.getLogger(IkaRuntime.class.getName()).log(
+                    Level.FINE,
+                    "Execution of command {0} in path: {1}",
+                    new Object[] { Arrays.toString(commandLine), path } );
 
-            ProcessBuilder pb = new ProcessBuilder(command);
+            ProcessBuilder pb = new ProcessBuilder(commandLine);
             pb.redirectErrorStream(true);
             pb.directory(path);
-            Process process = pb.start();
+            return pb.start();
+
+        } catch (IOException ex) {
+            String error = "cannot execute: " + Arrays.toString(commandLine);
 
-            InputStream is = process.getInputStream();
-            InputStreamReader reader = new InputStreamReader(is);
+            Logger.getLogger(IkaRuntime.class.getName()).
+                                                   log(Level.SEVERE, error, ex);
+            throw new IkaPluginException(error, ex);
+        }        
+    }
 
-            BufferedReader br = new BufferedReader(reader);
+    private static void trackProcess(RuntimeCommand command, Process proc) {
+        BufferedReader input = new BufferedReader(
+                                    new InputStreamReader(
+                                        proc.getInputStream() ));
 
-            String line;
-            while ((line = br.readLine()) != null) {
-                System.out.println(line);
+        String line;
+
+        registerViewerIfNeeded();
+
+        try {
+            while ((line = input.readLine()) != null) {
+                command.process(line);
             }
 
+            WorkerStatus lastStatus;
+            int exitStatus;
+
+            try {
+                exitStatus = proc.waitFor();
+                if (exitStatus != 0) {
+                    String status = "Terminated with status "
+                                                + Integer.toString(exitStatus);
+                    
+                    lastStatus = new DefaultWorkerStatus( command.getName(),
+                                                          1f,
+                                                          status);
+                }
+            } catch (InterruptedException ex) {
+                Logger.getLogger(IkaRuntime.class.getName()).log(Level.INFO,
+                        "Interrupted while waiting for command termination: "
+                        + command.getName(),
+                        ex);
+            }
+
+            StatusKeeper.getInstance().deregisterWorker(command.getName());
+            
         } catch (IOException ex) {
-            Logger.getLogger(IkaRuntime.class.getName()).
-                log(Level.SEVERE, "cannot execute: " + Arrays.toString(command), ex);
-            throw new IkaPluginException("cannot execute: " + Arrays.toString(command), ex);
+            Logger.getLogger(IkaRuntime.class.getName()).log(
+                   Level.SEVERE, command + ": error on the input stream", ex);
+            
+            throw new IkaPluginException("error on the input stream: "
+                                                                + command, ex);
+        }
+
+        deregisterViewerIfIdle(StatusKeeper.getInstance());
+    }
+
+    private static synchronized void registerViewerIfNeeded () {
+        if (StatusKeeper.getInstance().getStatusListenersCount() == 0) {
+            viewer = new UnformattedConsoleWriter(System.out);
+            StatusKeeper.getInstance().addStatusListener(viewer);
+        }
+    }
+
+    private static synchronized void deregisterViewerIfIdle (StatusKeeper sk) {
+        if (viewer != null && sk.getActiveTasksCount() == 0) {
+            sk.removeStatusListener(viewer);
+            viewer = null;
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/runtime/RuntimeCommand.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,100 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.runtime;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import org.icedrobot.ika.output.DefaultWorkerStatus;
+import org.icedrobot.ika.output.StatusKeeper;
+import org.icedrobot.ika.output.WorkerStatus;
+
+
+/**
+ * Executable command that will report its status to the StatusKeeper.
+ */
+public class RuntimeCommand {
+
+    private final String[] typeSample = new String[0];
+
+
+    private Process proc;
+    protected final String taskName;
+    protected List<String> commandLine;
+
+
+    /**
+     * Initializes a RuntimeCommand
+     * @param name          Command name. No other active commands must exist.
+     * @param commandLine   Command line used to execute the command.
+     */
+    public RuntimeCommand(String name, String... commandLine) { 
+        taskName = name;
+        this.commandLine = new LinkedList<String>(Arrays.asList(commandLine));
+
+        StatusKeeper.getInstance().registerWorker(taskName);
+    }
+
+
+    /**
+     * Parses a line of output provided by the external process.
+     * @param output    Output of the external process
+     * @return          WorkerStatus expressing the same information as output.
+     */
+    protected WorkerStatus parse(String output) {
+        //Remove escape sequences to avoid troubles
+        output = output.replaceAll("\33\\[=?\\d*(;\\d*)*\\w", "").trim();
+        return (output.isEmpty()) ? null
+                                :new DefaultWorkerStatus(taskName, 0f, output);
+    }
+
+    /**
+     * Processes a line of output provided by the external process.
+     */
+    protected void process(String output) {
+        WorkerStatus status = parse(output);
+        if (status != null) {
+            StatusKeeper.getInstance().reportStatus(status);
+        }
+    }
+
+    public String[] getCommandLine() {
+        return commandLine.toArray(typeSample);
+    }
+
+    public String getName() {
+        return taskName;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder bdr = new StringBuilder(getClass().getSimpleName());
+
+        bdr.append('(');
+
+        for (String s : commandLine) {
+            bdr.append(s);
+            bdr.append(' ');
+        }
+
+        bdr.setCharAt(bdr.length() - 1, ')');
+
+        return bdr.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/runtime/scm/GITCommand.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,29 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.runtime.scm;
+
+import org.icedrobot.ika.runtime.RuntimeCommand;
+
+class GITCommand extends RuntimeCommand{
+    public GITCommand (String name, String subcommand, String... arguments) {
+        super("git " + subcommand + " (" + name + ")", arguments);
+        this.commandLine.add(0, subcommand);
+        this.commandLine.add(0, "git");
+    }
+}
\ No newline at end of file
--- a/src/main/java/org/icedrobot/ika/runtime/scm/GITRepository.java	Mon Apr 11 22:16:19 2011 +0200
+++ b/src/main/java/org/icedrobot/ika/runtime/scm/GITRepository.java	Fri Apr 15 17:13:03 2011 +0200
@@ -22,9 +22,10 @@
 import java.io.IOException;
 import org.icedrobot.ika.plugins.IkaPluginException;
 import org.icedrobot.ika.runtime.IkaRuntime;
+import org.icedrobot.ika.runtime.RuntimeCommand;
 
 public class GITRepository extends Repository {
-    
+
     /**
      * Creates a new git Repository with the give name as ID, the
      * base directory as the location where the clone will be performed, the
@@ -61,32 +62,29 @@
     public void makeClone() {
         File git = new File(fullPath + File.separator + name +
                                        File.separator + ".git" );
+        RuntimeCommand command;
+
         if(!git.exists()) {
-            String command = "git clone --progress -b " +
-                              getBranch() + " " + remotePath;
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "clone", "--progress", "-b",
-                            getBranch(), remotePath);
+            command = new GITCommand(name, "clone", "--progress", "-b",
+                                        getBranch(), remotePath );
+            IkaRuntime.exec(getFullPath(), command);
         } else {
             File patchQueue = new File(fullPath + File.separator + name +
                                                   File.separator + ".git" +
                                                   File.separator + "patches" );
             if(patchQueue.exists()) {
-                String command = "guilt pop --all";
-                System.err.println(command);
+                command = new GuiltCommand(name, "pop", "--all");
                 IkaRuntime.exec(new File(getFullPath() + File.separator + name),
-                                "guilt", "pop", "--all");
+                                command);
             }
 
-            String command = "git fetch --progress " + remotePath;
-            System.err.println(command);
+            command = new GITCommand(name, "fetch", "--progress");
             IkaRuntime.exec(new File(getFullPath() + File.separator + name),
-                            "git", "fetch", "--progress", remotePath);
+                            command);
 
-            command = "git merge HEAD";
-            System.err.println(command);
+            command = new GITCommand(name, "merge", "HEAD");
             IkaRuntime.exec(new File(getFullPath() + File.separator + name),
-                            "git", "merge", "HEAD");
+                            command);
         }
     }
 
@@ -103,26 +101,21 @@
                                               File.separator + getBranch());
         if(!repoBranch.exists()) {
 
-            String command = "git init .";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "init");
+            RuntimeCommand command = new GITCommand(name, "init", ".");
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "git add --all";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "add", "--all");
+            command = new GITCommand(name, "add", "--all");
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "git commit -m initial_repository --verbose";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "commit", "-m",
-                            "initial_repository", "--verbose");
+            command = new GITCommand(name, "commit", "-m", "initial_repository",
+                                    "--verbose");
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "git branch " + getBranch();
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "branch", getBranch());
+            command = new GITCommand(name, "branch", getBranch());
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "git checkout " + getBranch();
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "git", "checkout", getBranch());
+            command = new GITCommand(name, "checkout", getBranch());
+            IkaRuntime.exec(getFullPath(), command);
 
             System.err.println(">>>>>>>> done: " + this + " <<<<<<<<");
         } else {
@@ -150,16 +143,13 @@
 
     @Override
     public void applyPatchQueue() {
-        String command = "guilt push --all";
-        System.err.println(command);
-        IkaRuntime.exec(getFullPath(), "guilt", "push", "--all");
+        GuiltCommand guilt = new GuiltCommand(name, "push", "--all");
+        IkaRuntime.exec(getFullPath(), guilt);
     }
 
     @Override
     public void initPatchQueue() {
-
-        String command = "guilt init";
-        System.err.println(command);
-        IkaRuntime.exec(getFullPath(), "guilt", "init");
+        GuiltCommand guilt = new GuiltCommand(name, "init");
+        IkaRuntime.exec(getFullPath(), guilt);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/runtime/scm/GuiltCommand.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,29 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.runtime.scm;
+
+import org.icedrobot.ika.runtime.RuntimeCommand;
+
+class GuiltCommand extends RuntimeCommand {
+    public GuiltCommand (String name, String subcommand, String... arguments) {
+        super("guilt" + subcommand + " (" + name + ")", arguments);
+        this.commandLine.add(0, subcommand);
+        this.commandLine.add(0, "guilt");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/java/org/icedrobot/ika/runtime/scm/HGCommand.java	Fri Apr 15 17:13:03 2011 +0200
@@ -0,0 +1,34 @@
+/*
+ * IKA - IcedRobot Kiosk Application
+ * Copyright (C) 2011  IcedRobot team
+ *
+ * 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, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.icedrobot.ika.runtime.scm;
+
+import org.icedrobot.ika.runtime.RuntimeCommand;
+
+/**
+ *
+ * @author giulio
+ */
+ class HgCommand extends RuntimeCommand {
+    public HgCommand (String name, String subcommand, String... arguments) {
+        super ("hg " + subcommand + " (" + name + ")", arguments);
+        this.commandLine.add(0, subcommand);
+        this.commandLine.add(0, "--debug");
+        this.commandLine.add(0, "hg");
+    }
+}
--- a/src/main/java/org/icedrobot/ika/runtime/scm/HGRepository.java	Mon Apr 11 22:16:19 2011 +0200
+++ b/src/main/java/org/icedrobot/ika/runtime/scm/HGRepository.java	Fri Apr 15 17:13:03 2011 +0200
@@ -20,6 +20,7 @@
 
 import java.io.File;
 import org.icedrobot.ika.runtime.IkaRuntime;
+import org.icedrobot.ika.runtime.RuntimeCommand;
 
 public class HGRepository extends Repository {
 
@@ -51,19 +52,18 @@
     @Override
     public void makeClone() {
         File hg = new File(this.fullPath + File.separator + ".hg");
+        RuntimeCommand command;
+
         if(!hg.exists()){
-            String command = "hg clone " + remotePath + " " + getFullPath();
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "clone",
-                            remotePath, getFullPath().toString());
+            command = new HgCommand(remotePath, "clone",
+                              remotePath, getFullPath().toString());
+            IkaRuntime.exec(getFullPath(), command);
         } else {
-            String command = "hg pull " + remotePath;
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "pull", remotePath);
+            command = new HgCommand(remotePath, "pull", remotePath);
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "hg update";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "update");
+            command = new HgCommand(remotePath, "update");
+            IkaRuntime.exec(getFullPath(), command);
         }
     }
 
@@ -92,18 +92,14 @@
     public void create() {
         File hg = new File(this.fullPath + File.separator + ".hg");
         if(!hg.exists()){
-            String command = "hg init .";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "init");
+            RuntimeCommand command = new HgCommand(name, "init", ".");
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "hg add";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "add");
+            command = new HgCommand(name, "add");
+            IkaRuntime.exec(getFullPath(), command);
 
-            command = "hg commit -minitial_repository";
-            System.err.println(command);
-            IkaRuntime.exec(getFullPath(), "hg", "commit",
-                            "-minitial_repository");
+            command = new HgCommand(name, "commit", "-minitial_repository");
+            IkaRuntime.exec(getFullPath(), command);
         } else {
             System.out.println("hg " + this + "allready exist");
         }