← Back to team overview

slub.team team mailing list archive

lp:~zeutschel/goobi-production/refactoring-filesystem-and-shellscript-functions into lp:goobi-production

 

Matthias Ronge has proposed merging lp:~zeutschel/goobi-production/refactoring-filesystem-and-shellscript-functions into lp:goobi-production.

Requested reviews:
  Ralf Claussnitzer (ralf-claussnitzer)

For more details, see:
https://code.launchpad.net/~zeutschel/goobi-production/refactoring-filesystem-and-shellscript-functions/+merge/103683

The locking issue was committed seperately as bug now. This is now real refactoring only. White spaces in script file paths have never been reportet as a problem but have been fixed “in passing” by using the appropriate object model, replacing the legacy way of concatenating command and arguments to a sting which is later split up into tokens again.
-- 
https://code.launchpad.net/~zeutschel/goobi-production/refactoring-filesystem-and-shellscript-functions/+merge/103683
Your team Saxon State Library Team is subscribed to branch lp:goobi-production.
=== modified file 'src/de/sub/goobi/beans/Benutzer.java'
--- src/de/sub/goobi/beans/Benutzer.java	2012-04-16 13:54:13 +0000
+++ src/de/sub/goobi/beans/Benutzer.java	2012-04-26 12:38:46 +0000
@@ -31,6 +31,7 @@
 import java.util.Set;
 
 import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 import dubious.sub.goobi.helper.encryption.DesEncrypter;
 import de.sub.goobi.helper.ldap.Ldap;
@@ -355,9 +356,7 @@
 		if (!rueckgabe.endsWith(File.separator))
 			rueckgabe += File.separator;
 		/* wenn das Verzeichnis nicht "" ist, aber noch nicht existiert, dann jetzt anlegen */
-		File homePath = new File(rueckgabe);
-		if (!homePath.exists())
-			new Helper().createUserDirectory(rueckgabe, login);
+		FilesystemHelper.createDirectoryForUser(rueckgabe, login);
 		return rueckgabe;
 	}
 

=== modified file 'src/de/sub/goobi/beans/Prozess.java'
--- src/de/sub/goobi/beans/Prozess.java	2012-04-13 13:17:45 +0000
+++ src/de/sub/goobi/beans/Prozess.java	2012-04-26 12:38:46 +0000
@@ -58,6 +58,7 @@
 import de.sub.goobi.persistence.BenutzerDAO;
 import de.sub.goobi.persistence.ProzessDAO;
 import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 import de.sub.goobi.helper.enums.MetadataFormat;
 import de.sub.goobi.helper.enums.StepStatus;
@@ -258,9 +259,7 @@
 			rueckgabe += File.separator;
 		}
 		if (!ConfigMain.getBooleanParameter("useOrigFolder", true) && ConfigMain.getBooleanParameter("createOrigFolderIfNotExists", false)) {
-			if (!new File(rueckgabe).exists()) {
-				new Helper().createMetaDirectory(rueckgabe);
-			}
+			FilesystemHelper.createDirectory(rueckgabe);
 		}
 		return rueckgabe;
 	}
@@ -312,8 +311,8 @@
 				origOrdner = DIRECTORY_PREFIX + "_" + titel + "_" + DIRECTORY_SUFFIX;
 			}
 			String rueckgabe = getImagesDirectory() + origOrdner + File.separator;
-			if (!new File(rueckgabe).exists() && ConfigMain.getBooleanParameter("createOrigFolderIfNotExists", false)) {
-				new Helper().createMetaDirectory(rueckgabe);
+			if (ConfigMain.getBooleanParameter("createOrigFolderIfNotExists", false)) {
+				FilesystemHelper.createDirectory(rueckgabe);
 			}
 			return rueckgabe;
 		} else {
@@ -323,8 +322,7 @@
 
 	public String getImagesDirectory() throws IOException, InterruptedException, SwapException, DAOException {
 		String pfad = getProcessDataDirectory() + "images" + File.separator;
-		if (!new File(pfad).exists())
-			new Helper().createMetaDirectory(pfad);
+		FilesystemHelper.createDirectory(pfad);
 		return pfad;
 	}
 
@@ -365,8 +363,7 @@
 	public String getProcessDataDirectoryIgnoreSwapping() throws IOException, InterruptedException, SwapException, DAOException {
 		String pfad = help.getGoobiDataDirectory() + id.intValue() + File.separator;
 		pfad = pfad.replaceAll(" ", "__");
-		if (!new File(pfad).exists())
-			new Helper().createMetaDirectory(pfad);
+		FilesystemHelper.createDirectory(pfad);
 		return pfad;
 	}
 
@@ -725,17 +722,6 @@
 		return result;
 	}
 
-	private void renameMetadataFile(String oldFileName, String newFileName) {
-		File oldFile;
-		File newFile;
-
-		if (oldFileName != null && newFileName != null) {
-			oldFile = new File(oldFileName);
-			newFile = new File(newFileName);
-			oldFile.renameTo(newFile);
-		}
-	}
-	
 	public void writeMetadataFile(Fileformat gdzfile) throws IOException, InterruptedException, SwapException, DAOException, WriteException,
 			PreferencesException {
 		Fileformat ff;
@@ -764,7 +750,7 @@
 		writeResult = ff.write(metadataFileNameNew);
 		if (writeResult) {
 			createBackupFile();
-			renameMetadataFile(metadataFileNameNew, metadataFileName);
+			FilesystemHelper.renameFile(metadataFileNameNew, metadataFileName);
 		}
 	}
 

=== modified file 'src/de/sub/goobi/config/ConfigMain.java'
--- src/de/sub/goobi/config/ConfigMain.java	2011-12-20 08:07:09 +0000
+++ src/de/sub/goobi/config/ConfigMain.java	2012-04-26 12:38:46 +0000
@@ -33,6 +33,7 @@
 import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
 import org.apache.log4j.Logger;
 
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 
 public class ConfigMain implements Serializable{
@@ -99,11 +100,8 @@
 			filename = session.getServletContext().getRealPath("/pages/imagesTemp") + File.separator;
 	
 			/* den Ordner neu anlegen, wenn er nicht existiert */
-			Helper help = new Helper();
 			try {
-				if (!new File(filename).exists()) {
-					help.createMetaDirectory(filename);
-				}
+				FilesystemHelper.createDirectory(filename);
 			} catch (Exception ioe) {
 				myLogger.error("IO error: " + ioe);
 				Helper.setFehlerMeldung(Helper.getTranslation("couldNotCreateImageFolder"), ioe.getMessage());

=== modified file 'src/de/sub/goobi/export/dms/ExportDms.java'
--- src/de/sub/goobi/export/dms/ExportDms.java	2012-02-22 07:43:02 +0000
+++ src/de/sub/goobi/export/dms/ExportDms.java	2012-04-26 12:38:46 +0000
@@ -44,6 +44,7 @@
 import de.sub.goobi.metadaten.MetadatenVerifizierung;
 import de.sub.goobi.config.ConfigMain;
 import de.sub.goobi.config.ConfigProjects;
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 import de.sub.goobi.helper.enums.MetadataFormat;
 import de.sub.goobi.helper.exceptions.DAOException;
@@ -284,7 +285,6 @@
 		/*
 		 * -------------------------------- dann den Ausgangspfad ermitteln --------------------------------
 		 */
-		Helper help = new Helper();
 		File tifOrdner = new File(myProzess.getImagesTifDirectory());
 
 		/*
@@ -301,7 +301,7 @@
 				/* wenn kein Agora-Import, dann den Ordner mit Benutzerberechtigung neu anlegen */
 				Benutzer myBenutzer = (Benutzer) Helper.getManagedBeanValue("#{LoginForm.myBenutzer}");
 				try {
-					help.createUserDirectory(zielTif.getAbsolutePath(), myBenutzer.getLogin());
+					FilesystemHelper.createDirectoryForUser(zielTif.getAbsolutePath(), myBenutzer.getLogin());
 				} catch (Exception e) {
 					Helper.setFehlerMeldung("Export canceled, error", "could not create destination directory");
 					myLogger.error("could not create destination directory", e);

=== modified file 'src/de/sub/goobi/export/download/ExportMets.java'
--- src/de/sub/goobi/export/download/ExportMets.java	2012-02-22 07:43:02 +0000
+++ src/de/sub/goobi/export/download/ExportMets.java	2012-04-26 12:38:46 +0000
@@ -50,6 +50,7 @@
 import de.sub.goobi.forms.LoginForm;
 import de.sub.goobi.metadaten.MetadatenImagesHelper;
 import de.sub.goobi.config.ConfigProjects;
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 import de.sub.goobi.helper.VariableReplacer;
 import de.sub.goobi.helper.exceptions.DAOException;
@@ -143,7 +144,7 @@
 		String target = inTargetFolder;
 		Benutzer myBenutzer = (Benutzer) Helper.getManagedBeanValue("#{LoginForm.myBenutzer}");
 		try {
-			help.createUserDirectory(target, myBenutzer.getLogin());
+			FilesystemHelper.createDirectoryForUser(target, myBenutzer.getLogin());
 		} catch (Exception e) {
 			Helper.setFehlerMeldung("Export canceled, could not create destination directory: " + inTargetFolder, e);
 		}

=== added file 'src/de/sub/goobi/helper/FilesystemHelper.java'
--- src/de/sub/goobi/helper/FilesystemHelper.java	1970-01-01 00:00:00 +0000
+++ src/de/sub/goobi/helper/FilesystemHelper.java	2012-04-26 12:38:46 +0000
@@ -0,0 +1,164 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package de.sub.goobi.helper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.SystemUtils;
+import org.apache.log4j.Logger;
+
+import de.sub.goobi.config.ConfigMain;
+
+/**
+ * Helper class for file system operations.
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public class FilesystemHelper {
+	private static final Logger logger = Logger
+			.getLogger(FilesystemHelper.class);
+
+	/**
+	 * Creates a directory with a name given. Under Linux a script is used to
+	 * set the file system permissions accordingly. This cannot be done from
+	 * within java code before version 1.7.
+	 * 
+	 * @param dirName
+	 *            Name of directory to create
+	 * @throws InterruptedException
+	 *             If the thread running the script is interrupted by another
+	 *             thread while it is waiting, then the wait is ended and an
+	 *             InterruptedException is thrown.
+	 * @throws IOException
+	 *             If an I/O error occurs.
+	 */
+	public static void createDirectory(String dirName) throws IOException,
+			InterruptedException {
+		if (!new File(dirName).exists()) {
+			ShellScript createDirScript = new ShellScript(new File(
+					ConfigMain.getParameter("script_createDirMeta")));
+			createDirScript.run(Arrays.asList(new String[] { dirName }));
+		}
+	}
+
+	/**
+	 * Creates a directory with a name given and assigns permissions to the
+	 * given user. Under Linux a script is used to set the file system
+	 * permissions accordingly. This cannot be done from within java code before
+	 * version 1.7.
+	 * 
+	 * @param dirName
+	 *            Name of directory to create
+	 * @throws InterruptedException
+	 *             If the thread running the script is interrupted by another
+	 *             thread while it is waiting, then the wait is ended and an
+	 *             InterruptedException is thrown.
+	 * @throws IOException
+	 *             If an I/O error occurs.
+	 */
+	public static void createDirectoryForUser(String dirName, String userName)
+			throws IOException, InterruptedException {
+		if (!new File(dirName).exists()) {
+			ShellScript createDirScript = new ShellScript(new File(
+					ConfigMain.getParameter("script_createDirUserHome")));
+			createDirScript.run(Arrays
+					.asList(new String[] { userName, dirName }));
+		}
+	}
+
+	public static void deleteSymLink(String symLink) {
+		String command = ConfigMain.getParameter("script_deleteSymLink");
+		ShellScript deleteSymLinkScript;
+		try {
+			deleteSymLinkScript = new ShellScript(new File(command));
+			deleteSymLinkScript.run(Arrays.asList(new String[] { symLink }));
+		} catch (FileNotFoundException e) {
+			logger.error("FileNotFoundException in deleteSymLink()", e);
+			Helper.setFehlerMeldung("Couldn't find script file, error",
+					e.getMessage());
+		} catch (IOException e) {
+			logger.error("IOException in deleteSymLink()", e);
+			Helper.setFehlerMeldung("Aborted deleteSymLink(), error",
+					e.getMessage());
+		} catch (InterruptedException e) {
+			logger.error("InterruptedException in deleteSymLink()", e);
+			Helper.setFehlerMeldung("Command '" + command
+					+ "' is interrupted in deleteSymLink()!");
+		}
+	}
+
+	/**
+	 * This function implements file renaming. Renaming of files is full of
+	 * mischief under Windows which unaccountably holds locks on files.
+	 * Sometimes running the JVM’s garbage collector puts things right.
+	 * 
+	 * @param oldFileName
+	 *            File to move or rename
+	 * @param newFileName
+	 *            New file name / destination
+	 * @throws IOException
+	 *             is thrown if the rename fails permanently
+	 */
+	public static void renameFile(String oldFileName, String newFileName)
+			throws IOException {
+		final int SLEEP_INTERVAL_MILLIS = 20;
+		final int MAX_WAIT_MILLIS = 150000; // 2½ minutes
+		File oldFile;
+		File newFile;
+		boolean success;
+		int millisWaited = 0;
+
+		if (oldFileName != null && newFileName != null) {
+			oldFile = new File(oldFileName);
+			newFile = new File(newFileName);
+			do {
+				if (SystemUtils.IS_OS_WINDOWS
+						&& millisWaited == SLEEP_INTERVAL_MILLIS) {
+					logger.warn("renameMetadataFile(): This is Windows. Running the garbage collector may yield good results. Forcing immediate garbage collection now!");
+					System.gc();
+				}
+				success = oldFile.renameTo(newFile);
+				if (!success) {
+					if (millisWaited == 0)
+						logger.info("renameMetadataFile(): Rename failed. File may be locked. Retrying.");
+					try {
+						Thread.sleep(SLEEP_INTERVAL_MILLIS);
+					} catch (InterruptedException e) {
+					}
+					millisWaited += SLEEP_INTERVAL_MILLIS;
+				}
+			} while (!success && millisWaited < MAX_WAIT_MILLIS);
+			if (!success) {
+				logger.error("renameMetadataFile(): Rename failed. This is a permanent error. Giving up.");
+				throw new IOException("Renaming of " + oldFileName + " into "
+						+ newFileName + " failed.");
+			} else if (millisWaited > 0)
+				logger.info("renameMetadataFile(): Rename finally succeeded after"
+						+ Integer.toString(millisWaited) + " milliseconds.");
+		}
+	}
+}

=== modified file 'src/de/sub/goobi/helper/Helper.java'
--- src/de/sub/goobi/helper/Helper.java	2012-02-24 13:46:03 +0000
+++ src/de/sub/goobi/helper/Helper.java	2012-04-26 12:38:46 +0000
@@ -44,18 +44,6 @@
 //TODO: Check if more method can be made static
 public class Helper implements Serializable, Observer {
 
-	// Pictures of the "Helper" also known as Tree-Man
-	// From http://monsterbrains.blogspot.com/
-	// http://i35.tinypic.com/20jmwes.jpg
-	// http://i38.tinypic.com/9jezh5.jpg
-	// Comic Reference
-	// http://katzundgoldt.de/port_laestiges_serviceunt_1.htm Panel 5
-	// References to the original
-	// http://upload.wikimedia.org/wikipedia/commons/9/91/Bosch_Jardin_des_delices_detail.jpg
-	// http://en.wikipedia.org/wiki/The_Garden_of_Earthly_Delights
-	// http://www.abcgallery.com/B/bosch/bosch1.html
-	// http://www.mesart.com/artworksps.jsp.que.artist.eq.678.amp.series.eq.4634.shtml
-
 	private static final Logger myLogger = Logger.getLogger(Helper.class);
 	private static final long serialVersionUID = -7449236652821237059L;
 
@@ -222,127 +210,6 @@
 		// Fix for Hibernate-Session-Management, old version - END
 	}
 
-	/* Helferklassen für kopieren von Verzeichnissen und Dateien */
-
-	/**
-	 * simple call of console command without any feedback, error handling or return value
-	 * ================================================================
-	 */
-	// TODO: Don't use this to create /pages/imagesTemp/
-	public static void callShell(String command) throws IOException, InterruptedException {
-		myLogger.debug("execute Shellcommand callShell: " + command);
-
-		if (isEmptyCommand(command)) {
-			return;
-		}
-
-		Process process = null;
-		try {
-			String[] commandToken = command.split("\\s");
-			process = new ProcessBuilder(commandToken).start();
-			process.waitFor();
-		} finally {
-			closeProcessStreams(process);
-		}
-	}
-
-	/**
-	 * Call scripts from console and give back error messages and return value of the called script
-	 * 
-	 */
-	public static Integer callShell2(String command) throws IOException, InterruptedException {
-		myLogger.debug("execute Shellcommand callShell2: " + command);
-		boolean errorsExist = false;
-		if (isEmptyCommand(command)) {
-			return 1;
-		}
-
-		Process process = null;
-		Scanner scanner = null;
-
-		try {
-			String[] commandToken = command.split("\\s");
-			process = new ProcessBuilder(commandToken).start();
-
-			scanner = new Scanner(process.getInputStream());
-			while (scanner.hasNextLine()) {
-				String myLine = scanner.nextLine();
-				setMeldung(myLine);
-			}
-			scanner.close();
-
-			scanner = new Scanner(process.getErrorStream());
-			while (scanner.hasNextLine()) {
-				errorsExist = true;
-				setFehlerMeldung(scanner.nextLine());
-			}
-			scanner.close();
-
-			int rueckgabe = process.waitFor();
-
-			if (errorsExist) {
-				return 1;
-			} else {
-				return rueckgabe;
-			}
-
-		} finally {
-			closeProcessStreams(process);
-
-			// HINT: Scanner implements Closeable on Java 1.7
-			if (scanner != null) {
-				scanner.close();
-			}
-		}
-	}
-
-	private static boolean isEmptyCommand(String command) {
-		return (command == null) || (command.length() == 0);
-	}
-	
-	public static void closeProcessStreams(Process process) {
-		if (process == null) {
-			return;
-		}
-
-		closeFile(process.getInputStream());
-		closeFile(process.getOutputStream());
-		closeFile(process.getErrorStream());
-	}
-
-	public static void closeFile (Closeable openFile) {
-		if (openFile == null) {
-			return;
-		}
-
-		try {
-			openFile.close();
-		} catch (IOException e) {
-			myLogger.warn("Could not close file.", e);
-			Helper.setFehlerMeldung("Could not close open file.");
-		}
-	}
-
-	// TODO: Move the Stuff below in a class for interaction with a local file system
-
-	public void createUserDirectory(String inDirPath, String inUser) throws IOException, InterruptedException {
-		/*
-		 * -------------------------------- Create directory with script --------------------------------
-		 */
-		String command = ConfigMain.getParameter("script_createDirUserHome") + " ";
-		command += inUser + " " + inDirPath;
-		callShell(command);
-	}
-
-	public void createMetaDirectory(String inDirPath) throws IOException, InterruptedException {
-		/*
-		 * -------------------------------- Create directory with script --------------------------------
-		 */
-		String command = ConfigMain.getParameter("script_createDirMeta") + " ";
-		command += inDirPath;
-		callShell(command);
-	}
-
 	public static void loadLanguageBundle() {
 		myLogger.info("Loading message bundles.");
 		bundle = ResourceBundle.getBundle("messages.messages", FacesContext.getCurrentInstance().getViewRoot().getLocale());

=== modified file 'src/de/sub/goobi/helper/HelperSchritte.java'
--- src/de/sub/goobi/helper/HelperSchritte.java	2012-02-22 07:43:02 +0000
+++ src/de/sub/goobi/helper/HelperSchritte.java	2012-04-26 12:38:46 +0000
@@ -208,7 +208,7 @@
 
 			try {
 				logger.info("Calling the shell: " + script);
-				int rueckgabe = Helper.callShell2(script);
+				int rueckgabe = ShellScript.legacyCallShell2(script);
 				if (fullautomatic) {
 					if (rueckgabe == 0)
 						closeStep(mySchritt, fullautomatic);

=== added file 'src/de/sub/goobi/helper/ShellScript.java'
--- src/de/sub/goobi/helper/ShellScript.java	1970-01-01 00:00:00 +0000
+++ src/de/sub/goobi/helper/ShellScript.java	2012-04-26 12:38:46 +0000
@@ -0,0 +1,261 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package de.sub.goobi.helper;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Scanner;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The class ShellScript is intended to run shell scripts (or other system
+ * commands).
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public class ShellScript {
+	private static final Logger logger = Logger.getLogger(ShellScript.class);
+
+	public static final int ERRORLEVEL_ERROR = 1;
+
+	private final String command;
+	private LinkedList<String> outputChannel, errorChannel;
+	private Integer errorLevel;
+
+	/**
+	 * This returns the command.
+	 * 
+	 * @return the command
+	 */
+	public File getCommand() {
+		return new File(command);
+	}
+
+	/**
+	 * This returns the command string.
+	 * 
+	 * @return the command
+	 */
+	public String getCommandString() {
+		return command;
+	}
+
+	/**
+	 * Provides the results of the script written on standard out. Null if the
+	 * script has not been run yet.
+	 * 
+	 * @return the output channel
+	 */
+	public LinkedList<String> getStdOut() {
+		return outputChannel;
+	}
+
+	/**
+	 * Provides the content of the standard error channel. Null if the script
+	 * has not been run yet.
+	 * 
+	 * @return the error channel
+	 */
+	public LinkedList<String> getStdErr() {
+		return errorChannel;
+	}
+
+	/**
+	 * Provides the result error level.
+	 * 
+	 * @return the error level
+	 */
+	public Integer getErrorLevel() {
+		return errorLevel;
+	}
+
+	/**
+	 * A shell script must be initialised with an existing file on the local
+	 * file system.
+	 * 
+	 * @param executable
+	 *            Script to run
+	 * @throws FileNotFoundException
+	 *             is thrown if the given executable does not exist.
+	 */
+	public ShellScript(File executable) throws FileNotFoundException {
+		if (!executable.exists())
+			throw new FileNotFoundException();
+		command = executable.getAbsolutePath();
+	}
+
+	/**
+	 * The function run() will execute the system command. This is a shorthand
+	 * to run the script without arguments.
+	 * 
+	 * @throws IOException
+	 *             If an I/O error occurs.
+	 * @throws InterruptedException
+	 *             If the current thread is interrupted by another thread while
+	 *             it is waiting, then the wait is ended and an
+	 *             InterruptedException is thrown.
+	 */
+	public int run() throws IOException, InterruptedException {
+		return run(null);
+	}
+
+	/**
+	 * The function run() will execute the system command. First, the call
+	 * sequence is created, including the parameters passed to run(). Then, the
+	 * underlying OS is contacted to run the command. Afterwards, the results
+	 * are being processed and stored.
+	 * 
+	 * The behaviour is slightly different from the legacy callShell2() command,
+	 * as it returns the error level as reported from the system process. Use
+	 * this to get the old behaviour:
+	 * 
+	 * <pre>
+	 *   Integer err = scr.run(args);
+	 *   if (scr.getStdErr().size() &gt; 0) err = ShellScript.ERRORLEVEL_ERROR;
+	 * </pre>
+	 * 
+	 * @param args
+	 *            A list of arguments passed to the script. May be null.
+	 * @throws IOException
+	 *             If an I/O error occurs.
+	 * @throws InterruptedException
+	 *             If the current thread is interrupted by another thread while
+	 *             it is waiting, then the wait is ended and an
+	 *             InterruptedException is thrown.
+	 */
+	public int run(List<String> args) throws IOException, InterruptedException {
+
+		List<String> commandLine = new ArrayList<String>();
+		commandLine.add(command);
+		if (args != null)
+			commandLine.addAll(args);
+		String[] callSequence = commandLine.toArray(new String[commandLine
+				.size()]);
+
+		Process process = null;
+		try {
+			process = new ProcessBuilder(callSequence).start();
+			outputChannel = inputStreamToLinkedList(process.getInputStream());
+			errorChannel = inputStreamToLinkedList(process.getErrorStream());
+		} finally {
+			closeStream(process.getInputStream());
+			closeStream(process.getOutputStream());
+			closeStream(process.getErrorStream());
+		}
+		errorLevel = process.waitFor();
+		return errorLevel;
+	}
+
+	/**
+	 * The function inputStreamToLinkedList() reads an InputStream and returns
+	 * it as a LinkedList.
+	 * 
+	 * @param myInputStream
+	 *            Stream to convert
+	 * @return A linked list holding the single lines.
+	 */
+	public static LinkedList<String> inputStreamToLinkedList(
+			InputStream myInputStream) {
+		LinkedList<String> result = new LinkedList<String>();
+		Scanner inputLines = null;
+		try {
+			inputLines = new Scanner(myInputStream);
+			while (inputLines.hasNextLine()) {
+				String myLine = inputLines.nextLine();
+				result.add(myLine);
+			}
+		} finally {
+			if (inputLines != null)
+				inputLines.close();
+		}
+		return result;
+	}
+
+	/**
+	 * This behaviour was already implemented. I can’t say if it’s necessary.
+	 * 
+	 * @param inputStream
+	 *            A stream to close.
+	 */
+	private static void closeStream(Closeable inputStream) {
+		if (inputStream == null)
+			return;
+		try {
+			inputStream.close();
+		} catch (IOException e) {
+			logger.warn("Could not close stream.", e);
+			Helper.setFehlerMeldung("Could not close open stream.");
+		}
+	}
+
+	/**
+	 * This implements the legacy Helper.callShell2() command. This is subject
+	 * to whitespace problems and is maintained here for backward compatibility
+	 * only. Please don’t use.
+	 * 
+	 * @param nonSpacesafeScriptingCommand
+	 *            A single line command which mustn’t contain parameters
+	 *            containing white spaces.
+	 * @return error level on success, 1 if an error occurs
+	 * @throws InterruptedException
+	 *             In case the script was interrupted due to concurrency
+	 * @throws IOException
+	 *             If an I/O error happens
+	 */
+	public static int legacyCallShell2(String nonSpacesafeScriptingCommand)
+			throws IOException, InterruptedException {
+		String[] tokenisedCommand = nonSpacesafeScriptingCommand.split("\\s");
+		ShellScript s;
+		int err = ShellScript.ERRORLEVEL_ERROR;
+		try {
+			s = new ShellScript(new File(tokenisedCommand[0]));
+			ArrayList<String> scriptingArgs = new ArrayList<String>();
+			for (int i = 1; i < tokenisedCommand.length; i++) {
+				scriptingArgs.add(tokenisedCommand[i]);
+			}
+			err = s.run(scriptingArgs);
+			for (String line : s.getStdOut()) {
+				Helper.setMeldung(line);
+			}
+			if (s.getStdErr().size() > 0) {
+				err = ShellScript.ERRORLEVEL_ERROR;
+				for (String line : s.getStdErr()) {
+					Helper.setFehlerMeldung(line);
+				}
+			}
+		} catch (FileNotFoundException e) {
+			logger.error("FileNotFoundException in callShell2()", e);
+			Helper.setFehlerMeldung(
+					"Couldn't find script file in callShell2(), error",
+					e.getMessage());
+		}
+		return err;
+	}
+}

=== modified file 'src/de/sub/goobi/helper/WebDav.java'
--- src/de/sub/goobi/helper/WebDav.java	2012-02-24 09:31:42 +0000
+++ src/de/sub/goobi/helper/WebDav.java	2012-04-26 12:38:46 +0000
@@ -102,18 +102,7 @@
 		}
 
 		for (String myname : inList) {
-			String command = ConfigMain.getParameter("script_deleteSymLink") + " ";
-			command += VerzeichnisAlle + myname;
-
-			try {
-				Helper.callShell(command);
-			} catch (java.io.IOException ioe) {
-				myLogger.error("IOException removeFromHomeAlle()", ioe);
-				Helper.setFehlerMeldung("Aborted removeFromHomeAlle(), error", ioe.getMessage());
-			} catch (InterruptedException ie) {
-				myLogger.error("InterruptedException in removeFromHomeAlle()", ie);
-				Helper.setFehlerMeldung("Command '" + command + "' is interrupted in removeFromHomeAlle()!");
-			}
+			FilesystemHelper.deleteSymLink(VerzeichnisAlle + myname);
 		}
 	}
 
@@ -142,18 +131,7 @@
 		nach = nach.replaceAll(" ", "__");
 		File benutzerHome = new File(nach);
 
-		String command = ConfigMain.getParameter("script_deleteSymLink") + " ";
-		command += benutzerHome;
-
-		try {
-			Helper.callShell(command);
-		} catch (java.io.IOException ioe) {
-			myLogger.error("IOException UploadFromHome", ioe);
-			Helper.setFehlerMeldung("Aborted upload from home, error", ioe.getMessage());
-		} catch (InterruptedException ie) {
-			myLogger.error("InterruptedException UploadFromHome", ie);
-			Helper.setFehlerMeldung("Command '" + command + "' is interrupted in UploadFromHome()!");
-		}
+		FilesystemHelper.deleteSymLink(benutzerHome.getAbsolutePath());
 	}
 
 	public void DownloadToHome(Prozess myProzess, int inSchrittID, boolean inNurLesen) {
@@ -171,11 +149,9 @@
 			/* bei Massendownload muss auch das Projekt- und Fertig-Verzeichnis existieren */
 			if (aktuellerBenutzer.isMitMassendownload()) {
 				File projekt = new File(userHome + myProzess.getProjekt().getTitel());
-				if (!projekt.exists())
-					help.createUserDirectory(projekt.getAbsolutePath(), aktuellerBenutzer.getLogin());
+				FilesystemHelper.createDirectoryForUser(projekt.getAbsolutePath(), aktuellerBenutzer.getLogin());
 				projekt = new File(userHome + "fertig" + File.separator);
-				if (!projekt.exists())
-					help.createUserDirectory(projekt.getAbsolutePath(), aktuellerBenutzer.getLogin());
+				FilesystemHelper.createDirectoryForUser(projekt.getAbsolutePath(), aktuellerBenutzer.getLogin());
 			}
 
 		} catch (Exception ioe) {
@@ -213,7 +189,7 @@
 		else
 			command += aktuellerBenutzer.getLogin();
 		try {
-			Helper.callShell2(command);
+			ShellScript.legacyCallShell2(command);
 		} catch (java.io.IOException ioe) {
 			myLogger.error("IOException DownloadToHome()", ioe);
 			Helper.setFehlerMeldung("Download aborted, IOException", ioe.getMessage());

=== modified file 'src/de/sub/goobi/helper/ldap/Ldap.java'
--- src/de/sub/goobi/helper/ldap/Ldap.java	2012-03-10 14:08:37 +0000
+++ src/de/sub/goobi/helper/ldap/Ldap.java	2012-04-26 12:38:46 +0000
@@ -52,6 +52,7 @@
 
 import de.sub.goobi.beans.Benutzer;
 import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.helper.FilesystemHelper;
 import de.sub.goobi.helper.Helper;
 
 public class Ldap {
@@ -101,7 +102,7 @@
 			String homePath = getUserHomeDirectory(inBenutzer);
 			if (!new File(homePath).exists()) {
 				myLogger.debug("HomeVerzeichnis existiert noch nicht");
-				new Helper().createUserDirectory(homePath, inBenutzer.getLogin());
+				FilesystemHelper.createDirectoryForUser(homePath, inBenutzer.getLogin());
 				myLogger.debug("HomeVerzeichnis angelegt");
 			} else
 				myLogger.debug("HomeVerzeichnis existiert schon");


Follow ups