From cfeeca159356b4ae818f50a9f16c7f8e3d934883 Mon Sep 17 00:00:00 2001
From: tpylak <tpylak>
Date: Fri, 7 Sep 2007 09:37:37 +0000
Subject: [PATCH] DMV refactoring to set clear dependencies between packages

SVN: 1638
---
 .../ch/systemsx/cisd/datamover/DataMover.java | 127 +++++++
 .../cisd/datamover/FSPathRemover.java         |  43 ---
 .../cisd/datamover/IncomingProcessor.java     | 298 +++++++++++++++
 .../cisd/datamover/IntraFSPathMover.java      |  84 -----
 ...cessorHandler.java => LocalProcessor.java} |  26 +-
 .../java/ch/systemsx/cisd/datamover/Main.java | 167 +--------
 .../cisd/datamover/MonitorStarter.java        | 348 ------------------
 .../systemsx/cisd/datamover/Parameters.java   |   4 +-
 .../ch/systemsx/cisd/datamover/SelfTest.java  |  64 +---
 .../filesystem/FileSysOparationsFactory.java  | 279 ++++++++++++++
 .../LocalFileSystem.java}                     |  64 ++--
 .../RemoteMonitoredMoverFactory.java          |  55 +++
 .../common}/CmdLineHelper.java                |   2 +-
 .../impl}/RecursiveHardLinkMaker.java         |   6 +-
 .../intf/IFileSysOperationsFactory.java       |   2 +-
 .../{ => filesystem}/intf/IPathCopier.java    |   2 +-
 .../intf/IPathImmutableCopier.java            |   2 +-
 .../{ => filesystem}/intf/IPathRemover.java   |   2 +-
 .../intf/IReadPathOperations.java             |   2 +-
 .../remote}/CopyActivityMonitor.java          |   4 +-
 .../remote}/RemotePathMover.java              |  11 +-
 .../remote}/XcopyCopier.java                  |   6 +-
 .../remote}/rsync/RsyncCopier.java            |  10 +-
 .../rsync/RsyncExitValueTranslator.java       |   2 +-
 .../remote}/rsync/RsyncVersionChecker.java    |   2 +-
 .../datamover/intf/IFileSysParameters.java    |  38 ++
 .../cisd/datamover/{ => utils}/FileStore.java |   6 +-
 .../{ => utils}/LazyPathHandler.java          |   2 +-
 .../{ => utils}/LocalBufferDirs.java          |  13 +-
 .../{helper => utils}/MarkerFile.java         |   2 +-
 .../{ => utils}/QuietPeriodFileFilter.java    |   4 +-
 .../ch/systemsx/cisd/datamover/MainTest.java  |   1 +
 .../systemsx/cisd/datamover/SelfTestTest.java |   3 +-
 .../impl}/RecursiveHardLinkMakerTest.java     |   5 +-
 .../remote}/CopyActivityMonitorTest.java      |   8 +-
 .../remote}/rsync/RsyncCopierTest.java        |   3 +-
 .../testhelper/FileStructEngine.java          |   2 +-
 37 files changed, 905 insertions(+), 794 deletions(-)
 create mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
 delete mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/FSPathRemover.java
 create mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
 delete mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/IntraFSPathMover.java
 rename datamover/source/java/ch/systemsx/cisd/datamover/{LocalProcessorHandler.java => LocalProcessor.java} (88%)
 delete mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/MonitorStarter.java
 create mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOparationsFactory.java
 rename datamover/source/java/ch/systemsx/cisd/datamover/{helper/FileSystemHelper.java => filesystem/LocalFileSystem.java} (71%)
 create mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RemoteMonitoredMoverFactory.java
 rename datamover/source/java/ch/systemsx/cisd/datamover/{helper => filesystem/common}/CmdLineHelper.java (98%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{hardlink => filesystem/impl}/RecursiveHardLinkMaker.java (96%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem}/intf/IFileSysOperationsFactory.java (95%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem}/intf/IPathCopier.java (98%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem}/intf/IPathImmutableCopier.java (95%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem}/intf/IPathRemover.java (96%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem}/intf/IReadPathOperations.java (96%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/CopyActivityMonitor.java (99%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/RemotePathMover.java (97%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{xcopy => filesystem/remote}/XcopyCopier.java (98%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/rsync/RsyncCopier.java (97%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/rsync/RsyncExitValueTranslator.java (98%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/rsync/RsyncVersionChecker.java (99%)
 create mode 100644 datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysParameters.java
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => utils}/FileStore.java (95%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => utils}/LazyPathHandler.java (98%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => utils}/LocalBufferDirs.java (76%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{helper => utils}/MarkerFile.java (97%)
 rename datamover/source/java/ch/systemsx/cisd/datamover/{ => utils}/QuietPeriodFileFilter.java (94%)
 rename datamover/sourceTest/java/ch/systemsx/cisd/datamover/{hardlink => filesystem/impl}/RecursiveHardLinkMakerTest.java (96%)
 rename datamover/sourceTest/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/CopyActivityMonitorTest.java (97%)
 rename datamover/sourceTest/java/ch/systemsx/cisd/datamover/{ => filesystem/remote}/rsync/RsyncCopierTest.java (97%)

diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java b/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
new file mode 100644
index 00000000000..37ec4f8c3ac
--- /dev/null
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/DataMover.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 ch.systemsx.cisd.datamover;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.utilities.ITerminable;
+import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler;
+import ch.systemsx.cisd.datamover.filesystem.RemoteMonitoredMoverFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IFileSysOperationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.utils.FileStore;
+import ch.systemsx.cisd.datamover.utils.LazyPathHandler;
+import ch.systemsx.cisd.datamover.utils.LocalBufferDirs;
+
+/**
+ * A class that starts up the processing pipeline and its monitoring, based on the parameters provided.
+ * 
+ * @author Bernd Rinn
+ * @author Tomasz Pylak on Aug 24, 2007
+ */
+public class DataMover
+{
+    private final static String LOCAL_COPY_IN_PROGRESS_DIR = "copy-in-progress";
+
+    private final static String LOCAL_COPY_COMPLETE_DIR = "copy-complete";
+
+    private final static String LOCAL_READY_TO_MOVE_DIR = "ready-to-move";
+
+    private final static String LOCAL_TEMP_DIR = "tmp";
+
+    private final Parameters parameters;
+
+    private final IFileSysOperationsFactory factory;
+
+    private final LocalBufferDirs bufferDirs;
+
+    /**
+     * starts the process of moving data and monitoring it
+     * 
+     * @return object which can be used to terminate the process and all its threads
+     */
+    public static final ITerminable start(Parameters parameters, IFileSysOperationsFactory factory)
+    {
+        LocalBufferDirs localBufferDirs =
+                new LocalBufferDirs(parameters, LOCAL_COPY_IN_PROGRESS_DIR, LOCAL_COPY_COMPLETE_DIR,
+                        LOCAL_READY_TO_MOVE_DIR, LOCAL_TEMP_DIR);
+        return start(parameters, factory, localBufferDirs);
+    }
+
+    /** Allows to specify buffer directories. Exposed for testing purposes. */
+    public static final ITerminable start(Parameters parameters, IFileSysOperationsFactory factory,
+            LocalBufferDirs localBufferDirs)
+    {
+        return new DataMover(parameters, factory, localBufferDirs).start();
+    }
+
+    private DataMover(Parameters parameters, IFileSysOperationsFactory factory, LocalBufferDirs bufferDirs)
+    {
+        this.parameters = parameters;
+        this.factory = factory;
+        this.bufferDirs = bufferDirs;
+    }
+
+    private ITerminable start()
+    {
+        LazyPathHandler outgoingProcessor = startupOutgoingMovingProcess(parameters.getOutgoingStore());
+        LazyPathHandler localProcessor = startupLocalProcessing(outgoingProcessor);
+        ITerminable incomingProcessor = startupIncomingMovingProcess(localProcessor);
+        return createTerminable(outgoingProcessor, localProcessor, incomingProcessor);
+    }
+
+    private ITerminable startupIncomingMovingProcess(IPathHandler localProcessor)
+    {
+        return IncomingProcessor.startupMovingProcess(parameters, factory, bufferDirs, localProcessor);
+    }
+
+    private LazyPathHandler startupLocalProcessing(LazyPathHandler outgoingHandler)
+    {
+        final IPathImmutableCopier copier = factory.getImmutableCopier();
+        final IPathHandler localProcesingHandler =
+                LocalProcessor.createAndRecover(parameters, bufferDirs.getCopyCompleteDir(), bufferDirs
+                        .getReadyToMoveDir(), bufferDirs.getTempDir(), outgoingHandler, copier);
+        return LazyPathHandler.create(localProcesingHandler, "Local Processor");
+    }
+
+    private LazyPathHandler startupOutgoingMovingProcess(FileStore outputDir)
+    {
+        final IPathHandler remoteMover = createRemotePathMover(null, outputDir.getPath(), outputDir.getHost());
+        return LazyPathHandler.create(remoteMover, "Final Destination Mover");
+    }
+
+    private IPathHandler createRemotePathMover(String sourceHost, File destinationDirectory, String destinationHost)
+    {
+        return RemoteMonitoredMoverFactory.create(sourceHost, destinationDirectory, destinationHost, factory, parameters);
+    }
+
+    private static ITerminable createTerminable(final ITerminable... terminables)
+    {
+        return new ITerminable()
+            {
+                public boolean terminate()
+                {
+                    boolean ok = true;
+                    for (int i = 0; i < terminables.length; i++)
+                    {
+                        ok = ok && terminables[i].terminate();
+                    }
+                    return ok;
+                }
+            };
+    }
+}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/FSPathRemover.java b/datamover/source/java/ch/systemsx/cisd/datamover/FSPathRemover.java
deleted file mode 100644
index 0e26dd1d1e5..00000000000
--- a/datamover/source/java/ch/systemsx/cisd/datamover/FSPathRemover.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * 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 ch.systemsx.cisd.datamover;
-
-import java.io.File;
-
-import ch.systemsx.cisd.common.exceptions.Status;
-import ch.systemsx.cisd.common.exceptions.StatusFlag;
-import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.intf.IPathRemover;
-
-/**
- * Removes a path (file or directory) from the file system, if necessary recursively.
- * 
- * @author Bernd Rinn
- */
-public class FSPathRemover implements IPathRemover
-{
-
-    private final Status STATUS_FAILED_DELETION = new Status(StatusFlag.FATAL_ERROR, "Failed to remove path.");
-
-    public Status remove(File path)
-    {
-        final boolean deletionOK = FileUtilities.deleteRecursively(path);
-
-        return deletionOK ? Status.OK : STATUS_FAILED_DELETION;
-    }
-
-}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
new file mode 100644
index 00000000000..5811e08bdab
--- /dev/null
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/IncomingProcessor.java
@@ -0,0 +1,298 @@
+package ch.systemsx.cisd.datamover;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.Timer;
+
+import ch.systemsx.cisd.common.Constants;
+import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask;
+import ch.systemsx.cisd.common.utilities.ITerminable;
+import ch.systemsx.cisd.common.utilities.NamePrefixFileFilter;
+import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler;
+import ch.systemsx.cisd.datamover.filesystem.LocalFileSystem;
+import ch.systemsx.cisd.datamover.filesystem.RemoteMonitoredMoverFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IFileSysOperationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IReadPathOperations;
+import ch.systemsx.cisd.datamover.utils.FileStore;
+import ch.systemsx.cisd.datamover.utils.LocalBufferDirs;
+import ch.systemsx.cisd.datamover.utils.MarkerFile;
+import ch.systemsx.cisd.datamover.utils.QuietPeriodFileFilter;
+
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+/**
+ * @author Tomasz Pylak on Sep 7, 2007
+ */
+public class IncomingProcessor
+{
+    private final Parameters parameters;
+
+    private final IFileSysOperationsFactory factory;
+
+    private final IReadPathOperations incomingReadOperations;
+
+    private final LocalBufferDirs bufferDirs;
+
+    private final boolean isIncomingRemote;
+
+    private final String prefixForIncoming;
+
+    public static final ITerminable startupMovingProcess(Parameters parameters, IFileSysOperationsFactory factory,
+            LocalBufferDirs bufferDirs, IPathHandler localProcessor)
+    {
+        IncomingProcessor processor = new IncomingProcessor(parameters, factory, bufferDirs);
+        FileStore incomingStore = parameters.getIncomingStore();
+
+        processor.recoverIncomingAfterShutdown(incomingStore, localProcessor);
+        return processor.startupIncomingMovingProcess(incomingStore, localProcessor);
+    }
+
+    private IncomingProcessor(Parameters parameters, IFileSysOperationsFactory factory, LocalBufferDirs bufferDirs)
+    {
+        this.parameters = parameters;
+        this.prefixForIncoming = parameters.getPrefixForIncoming();
+        this.isIncomingRemote = parameters.getTreatIncomingAsRemote();
+        this.incomingReadOperations = factory.getReadAccessor();
+        this.factory = factory;
+        this.bufferDirs = bufferDirs;
+    }
+
+    private void recoverIncomingAfterShutdown(FileStore incomingStore, IPathHandler localProcessor)
+    {
+        new IncomingProcessorRecovery().recoverIncomingAfterShutdown(incomingStore, localProcessor);
+    }
+
+    private ITerminable startupIncomingMovingProcess(FileStore incomingStore, IPathHandler localProcessor)
+    {
+        IPathHandler pathHandler = createIncomingMovingPathHandler(incomingStore.getHost(), localProcessor);
+        FileFilter filter = createQuitePeriodFilter();
+
+        final DirectoryScanningTimerTask movingTask =
+                new DirectoryScanningTimerTask(incomingStore.getPath(), filter, pathHandler);
+        final Timer movingTimer = new Timer("Mover of Incomming Data");
+        // The moving task is scheduled at fixed rate. It makes sense especially if the task is moving data from the
+        // remote share. The rationale behind this is that if new items are
+        // added to the source directory while the remote timer task has been running for a long time, busy moving data,
+        // the task shoulnd't sit idle for the check time when there is actually work to do.
+        movingTimer.scheduleAtFixedRate(movingTask, 0, parameters.getCheckIntervalMillis());
+        return asTerminable(movingTimer);
+    }
+
+    private static ITerminable asTerminable(final Timer timer)
+    {
+        return new ITerminable()
+            {
+                public boolean terminate()
+                {
+                    timer.cancel();
+                    return true;
+                }
+            };
+    }
+
+    private FileFilter createQuitePeriodFilter()
+    {
+        FileFilter quitePeriodFilter = new QuietPeriodFileFilter(parameters, incomingReadOperations);
+        FileFilter filterDeletionMarkers = new NamePrefixFileFilter(Constants.DELETION_IN_PROGRESS_PREFIX, false);
+        FileFilter filter = combineFilters(filterDeletionMarkers, quitePeriodFilter);
+        return filter;
+    }
+
+    private static FileFilter combineFilters(final FileFilter filter1, final FileFilter filter2)
+    {
+        return new FileFilter()
+            {
+                public boolean accept(File pathname)
+                {
+                    return filter1.accept(pathname) && filter2.accept(pathname);
+                }
+            };
+    }
+
+    private IPathHandler createIncomingMovingPathHandler(final String sourceHostOrNull,
+            final IPathHandler localProcessor)
+    {
+        return new IPathHandler()
+            {
+                public boolean handle(File sourceFile)
+                {
+                    if (isIncomingRemote)
+                    {
+                        return moveFromRemoteIncoming(sourceFile, sourceHostOrNull, localProcessor);
+                    } else
+                    {
+                        return moveFromLocalIncoming(sourceFile, localProcessor);
+                    }
+                }
+            };
+    }
+
+    private boolean moveFromLocalIncoming(File source, IPathHandler localProcessor)
+    {
+        final File finalFile = tryMoveLocal(source, bufferDirs.getCopyCompleteDir(), parameters.getPrefixForIncoming());
+        if (finalFile == null)
+        {
+            return false;
+        }
+        return localProcessor.handle(finalFile);
+    }
+
+    private boolean moveFromRemoteIncoming(File source, String sourceHostOrNull, IPathHandler localProcessor)
+    {
+        // 1. move from incoming: copy, delete, create copy-finished-marker
+        final File copyInProgressDir = bufferDirs.getCopyInProgressDir();
+        final boolean ok = moveFromRemoteToLocal(source, sourceHostOrNull, copyInProgressDir);
+        if (ok == false)
+        {
+            return false;
+        }
+        final File copiedFile = new File(copyInProgressDir, source.getName());
+        assert copiedFile.exists() : copiedFile.getAbsolutePath();
+        final File markerFile = MarkerFile.createCopyFinishedMarker(copiedFile);
+        assert markerFile.exists() : markerFile.getAbsolutePath();
+
+        // 2. Move to final directory, delete marker
+        final File finalFile = tryMoveFromInProgressToFinished(copiedFile, markerFile, bufferDirs.getCopyCompleteDir());
+        if (finalFile == null)
+        {
+            return false;
+        }
+
+        // 3. schedule local processing, always successful
+        localProcessor.handle(finalFile);
+        return true;
+    }
+
+    private File tryMoveFromInProgressToFinished(File copiedFile, File markerFileOrNull, File copyCompleteDir)
+    {
+        final File finalFile = tryMoveLocal(copiedFile, copyCompleteDir, prefixForIncoming);
+        if (finalFile != null)
+        {
+            if (markerFileOrNull != null)
+            {
+                markerFileOrNull.delete(); // process even if marker file could not be deleted
+            }
+            return finalFile;
+        } else
+        {
+            return null;
+        }
+    }
+
+    private boolean moveFromRemoteToLocal(File source, String sourceHostOrNull, File localDestDir)
+    {
+        return createRemotePathMover(sourceHostOrNull, localDestDir, null).handle(source);
+    }
+
+    private IPathHandler createRemotePathMover(String sourceHost, File destinationDirectory, String destinationHost)
+    {
+        return RemoteMonitoredMoverFactory.create(sourceHost, destinationDirectory, destinationHost, factory, parameters);
+    }
+
+    private static File tryMoveLocal(File sourceFile, File destinationDir, String prefixTemplate)
+    {
+        return LocalFileSystem.tryMoveLocal(sourceFile, destinationDir, prefixTemplate);
+    }
+
+    // ------------------- recovery ------------------------
+
+    class IncomingProcessorRecovery
+    {
+        public void recoverIncomingAfterShutdown(FileStore incomingStore, IPathHandler localProcessor)
+        {
+            if (isIncomingRemote)
+            {
+                recoverIncomingInProgress(incomingStore, bufferDirs.getCopyInProgressDir(), bufferDirs
+                        .getCopyCompleteDir());
+            }
+            recoverIncomingCopyComplete(bufferDirs.getCopyCompleteDir(), localProcessor);
+        }
+
+        private void recoverIncomingInProgress(FileStore incomingStore, File copyInProgressDir, File copyCompleteDir)
+        {
+            final File[] files = LocalFileSystem.listFiles(copyInProgressDir);
+            if (files == null || files.length == 0)
+            {
+                return; // directory is empty, no recovery is needed
+            }
+
+            for (File file : files)
+            {
+                if (MarkerFile.isDeletionInProgressMarker(file))
+                {
+                    continue;
+                }
+                recoverIncomingAfterShutdown(file, incomingStore, copyCompleteDir);
+            }
+        }
+
+        private void recoverIncomingAfterShutdown(File unfinishedFile, FileStore incomingStore, File copyCompleteDir)
+        {
+            if (MarkerFile.isCopyFinishedMarker(unfinishedFile))
+            {
+                final File markerFile = unfinishedFile;
+                final File localCopy = MarkerFile.extractOriginalFromCopyFinishedMarker(markerFile);
+                if (localCopy.exists())
+                {
+                    // copy and marker exist - do nothing, recovery will be done for copied resource
+                } else
+                {
+                    // copy finished, resource moved, but marker was not deleted
+                    markerFile.delete();
+                }
+            } else
+            // handle local copy
+            {
+                final File localCopy = unfinishedFile;
+                final File markerFile = MarkerFile.createCopyFinishedMarker(localCopy);
+                if (markerFile.exists())
+                {
+                    // copy and marker exist - copy finished, but copied resource not moved
+                    tryMoveFromInProgressToFinished(localCopy, markerFile, copyCompleteDir);
+                } else
+                // no marker
+                {
+                    File incomingDir = incomingStore.getPath();
+                    File originalInIncoming = new File(incomingDir, localCopy.getName());
+                    if (incomingReadOperations.exists(originalInIncoming))
+                    {
+                        // partial copy - nothing to do, will be copied again
+                    } else
+                    {
+                        // move finished, but marker not created
+                        tryMoveFromInProgressToFinished(localCopy, null, copyCompleteDir);
+                    }
+                }
+            }
+        }
+
+        // schedule processing of all resources which were previously copied
+        private void recoverIncomingCopyComplete(File copyCompleteDir, IPathHandler localProcessor)
+        {
+            final File[] files = LocalFileSystem.listFiles(copyCompleteDir);
+            if (files == null || files.length == 0)
+            {
+                return; // directory is empty, no recovery is needed
+            }
+
+            for (File file : files)
+            {
+                localProcessor.handle(file);
+            }
+        }
+    }
+}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/IntraFSPathMover.java b/datamover/source/java/ch/systemsx/cisd/datamover/IntraFSPathMover.java
deleted file mode 100644
index 3e74872d603..00000000000
--- a/datamover/source/java/ch/systemsx/cisd/datamover/IntraFSPathMover.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * 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 ch.systemsx.cisd.datamover;
-
-import java.io.File;
-
-import org.apache.log4j.Logger;
-
-import ch.systemsx.cisd.common.logging.LogCategory;
-import ch.systemsx.cisd.common.logging.LogFactory;
-import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask;
-import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
-
-/**
- * A {@link DirectoryScanningTimerTask.IPathHandler} that moves paths out of the way within one file system by calling
- * {@link File#renameTo(File)}..
- * 
- * @author Bernd Rinn
- */
-public class IntraFSPathMover implements DirectoryScanningTimerTask.IPathHandler
-{
-
-    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, IntraFSPathMover.class);
-
-    private static final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, IntraFSPathMover.class);
-
-    private final File destinationDirectory;
-
-    private final String prefixTemplate;
-
-    /**
-     * Creates a <var>PathMover</var>.
-     * 
-     * @param destinationDirectory The directory to move paths to.
-     */
-    public IntraFSPathMover(File destinationDirectory, String prefixTemplate)
-    {
-        assert destinationDirectory != null;
-        assert FileUtilities.checkDirectoryFullyAccessible(destinationDirectory, "destination") == null : FileUtilities
-                .checkDirectoryFullyAccessible(destinationDirectory, "destination");
-        assert prefixTemplate != null;
-
-        this.destinationDirectory = destinationDirectory;
-        this.prefixTemplate = prefixTemplate;
-    }
-
-    public boolean handle(File path)
-    {
-        assert path != null;
-        assert destinationDirectory != null;
-        assert prefixTemplate != null;
-
-        final String destinationPath =
-                FileSystemHelper.createDestinationPath(path, null, destinationDirectory, prefixTemplate);
-        if (operationLog.isInfoEnabled())
-        {
-            operationLog
-                    .info(String.format("Moving path '%s' to '%s'", path.getPath(), destinationPath));
-        }
-        boolean movedOK = path.renameTo(new File(destinationPath));
-        if (movedOK == false)
-        {
-            notificationLog.error(String.format("Moving path '%s' to directory '%s' failed.", path,
-                    destinationDirectory));
-        }
-        return movedOK;
-    }
-
-}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessorHandler.java b/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
similarity index 88%
rename from datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessorHandler.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
index 4d119884855..7824f921a8b 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessorHandler.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/LocalProcessor.java
@@ -30,8 +30,9 @@ import ch.systemsx.cisd.common.utilities.FileUtilities;
 import ch.systemsx.cisd.common.utilities.RegexFileFilter;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler;
 import ch.systemsx.cisd.common.utilities.RegexFileFilter.PathType;
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
-import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.filesystem.LocalFileSystem;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.utils.LazyPathHandler;
 
 /**
  * Processing of the files on the local machine. This class does not scan its input directory, all resources must
@@ -39,9 +40,9 @@ import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
  * 
  * @author Tomasz Pylak on Aug 24, 2007
  */
-public class LocalProcessorHandler implements IPathHandler
+public class LocalProcessor implements IPathHandler
 {
-    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, LocalProcessorHandler.class);
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, LocalProcessor.class);
 
     private final Parameters parameters;
 
@@ -60,7 +61,7 @@ public class LocalProcessorHandler implements IPathHandler
 
     private final LazyPathHandler outgoingHandler;
 
-    private LocalProcessorHandler(Parameters parameters, File outputDir, File tempDir, LazyPathHandler outgoingHandler,
+    private LocalProcessor(Parameters parameters, File outputDir, File tempDir, LazyPathHandler outgoingHandler,
             IPathImmutableCopier copier)
     {
         this.parameters = parameters;
@@ -74,8 +75,7 @@ public class LocalProcessorHandler implements IPathHandler
     public static final IPathHandler createAndRecover(Parameters parameters, File inputDir, File outputDir,
             File bufferDir, LazyPathHandler lastStepHandler, IPathImmutableCopier copier)
     {
-        LocalProcessorHandler handler =
-                new LocalProcessorHandler(parameters, outputDir, bufferDir, lastStepHandler, copier);
+        LocalProcessor handler = new LocalProcessor(parameters, outputDir, bufferDir, lastStepHandler, copier);
         handler.recoverAfterShutdown(inputDir);
         return handler;
     }
@@ -90,7 +90,7 @@ public class LocalProcessorHandler implements IPathHandler
 
     private static void recoverTemporaryExtraCopy(File tempDir, File inputDir, File extraCopyDirOrNull)
     {
-        final File[] files = FileSystemHelper.listFiles(tempDir);
+        final File[] files = LocalFileSystem.listFiles(tempDir);
         if (files == null || files.length == 0)
         {
             return; // directory is empty, no recovery is needed
@@ -109,7 +109,7 @@ public class LocalProcessorHandler implements IPathHandler
                 // could change. We move the copy to that directory to ensure clean recovery from errors.
                 if (extraCopyDirOrNull != null)
                 {
-                    FileSystemHelper.tryMoveLocal(file, extraCopyDirOrNull);
+                    LocalFileSystem.tryMoveLocal(file, extraCopyDirOrNull);
                 }
             }
         }
@@ -122,7 +122,7 @@ public class LocalProcessorHandler implements IPathHandler
 
     private static void recoverRegisterReadyForOutgoing(File outputDir, IPathHandler outgoingHandler)
     {
-        File[] files = FileSystemHelper.listFiles(outputDir);
+        File[] files = LocalFileSystem.listFiles(outputDir);
         if (files == null || files.length == 0)
         {
             return; // directory is empty, no recovery is needed
@@ -152,7 +152,7 @@ public class LocalProcessorHandler implements IPathHandler
             ok = ok && (extraTmpCopy != null);
         }
 
-        final File movedFile = FileSystemHelper.tryMoveLocal(path, outputDir);
+        final File movedFile = LocalFileSystem.tryMoveLocal(path, outputDir);
         if (movedFile != null)
         {
             outgoingHandler.handle(movedFile);
@@ -162,7 +162,7 @@ public class LocalProcessorHandler implements IPathHandler
         if (extraTmpCopy != null)
         {
             assert extraCopyDirOrNull != null;
-            File extraCopy = FileSystemHelper.tryMoveLocal(extraTmpCopy, extraCopyDirOrNull);
+            File extraCopy = LocalFileSystem.tryMoveLocal(extraTmpCopy, extraCopyDirOrNull);
             ok = ok && (extraCopy != null);
         }
         return ok;
@@ -226,7 +226,7 @@ public class LocalProcessorHandler implements IPathHandler
         {
             log(resource, "Moving to manual intervention directory");
             File manualInterventionDir = parameters.getManualInterventionDirectory();
-            File movedFile = FileSystemHelper.tryMoveLocal(resource, manualInterventionDir);
+            File movedFile = LocalFileSystem.tryMoveLocal(resource, manualInterventionDir);
             return (movedFile != null) ? EFileManipResult.STOP : EFileManipResult.FAILURE;
         } else
         {
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/Main.java b/datamover/source/java/ch/systemsx/cisd/datamover/Main.java
index 418577250b4..e2b25e9261a 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/Main.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/Main.java
@@ -16,31 +16,22 @@
 
 package ch.systemsx.cisd.datamover;
 
-import java.io.File;
 import java.lang.Thread.UncaughtExceptionHandler;
 import java.util.ArrayList;
 
 import org.apache.log4j.Logger;
 
-import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.HighLevelException;
-import ch.systemsx.cisd.common.exceptions.Status;
-import ch.systemsx.cisd.common.exceptions.StatusFlag;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.BuildAndEnvironmentInfo;
 import ch.systemsx.cisd.common.utilities.ITerminable;
-import ch.systemsx.cisd.common.utilities.OSUtilities;
-import ch.systemsx.cisd.datamover.hardlink.RecursiveHardLinkMaker;
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
-import ch.systemsx.cisd.datamover.intf.IFileSysOperationsFactory;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
-import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
-import ch.systemsx.cisd.datamover.intf.IPathRemover;
-import ch.systemsx.cisd.datamover.rsync.RsyncCopier;
-import ch.systemsx.cisd.datamover.xcopy.XcopyCopier;
+import ch.systemsx.cisd.datamover.filesystem.FileSysOparationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IFileSysOperationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.utils.FileStore;
+import ch.systemsx.cisd.datamover.utils.LocalBufferDirs;
 
 /**
  * The main class of the datamover.
@@ -78,144 +69,6 @@ public class Main
         parameters.log();
     }
 
-    private static IPathImmutableCopier getImmutablePathCopier(Parameters parameters)
-    {
-        String lnExec = parameters.getHardLinkExecutable();
-        if (lnExec != null)
-        {
-            return RecursiveHardLinkMaker.create(lnExec);
-        }
-
-        IPathImmutableCopier copier = null;
-        if (OSUtilities.isWindows() == false)
-        {
-            copier = RecursiveHardLinkMaker.tryCreate();
-            if (copier != null)
-            {
-                return copier;
-            }
-        }
-        return createFakedImmCopier(parameters);
-    }
-
-    private static IPathImmutableCopier createFakedImmCopier(Parameters parameters)
-    {
-        final IPathCopier normalCopier = suggestPathCopier(parameters, false);
-        return new IPathImmutableCopier()
-            {
-                public File tryCopy(File file, File destinationDirectory)
-                {
-                    Status status = normalCopier.copy(file, destinationDirectory);
-                    if (StatusFlag.OK.equals(status.getFlag()))
-                    {
-                        return new File(destinationDirectory, file.getName());
-                    } else
-                    {
-                        notificationLog.error(String.format("Copy of '%s' to '%s' failed: %s.", file.getPath(),
-                                destinationDirectory.getPath(), status));
-                        return null;
-                    }
-                }
-            };
-    }
-
-    private static IPathCopier suggestPathCopier(Parameters parameters, boolean requiresDeletionBeforeCreation)
-    {
-        final File rsyncExecutable = findRsyncExecutable(parameters.getRsyncExecutable());
-        final File sshExecutable = findSshExecutable(parameters.getSshExecutable());
-        if (rsyncExecutable != null)
-        {
-            return new RsyncCopier(rsyncExecutable, sshExecutable, requiresDeletionBeforeCreation);
-        } else if (OSUtilities.isWindows())
-        {
-            return new XcopyCopier(OSUtilities.findExecutable("xcopy"), requiresDeletionBeforeCreation);
-        } else
-        {
-            throw new ConfigurationFailureException("Unable to find a copy engine.");
-        }
-    }
-
-    private static File findRsyncExecutable(final String rsyncExecutablePath)
-    {
-        final File rsyncExecutable;
-        if (rsyncExecutablePath != null)
-        {
-            rsyncExecutable = new File(rsyncExecutablePath);
-        } else if (OSUtilities.isWindows() == false)
-        {
-            rsyncExecutable = OSUtilities.findExecutable("rsync");
-        } else
-        {
-            rsyncExecutable = null;
-        }
-        if (rsyncExecutable != null && OSUtilities.executableExists(rsyncExecutable) == false)
-        {
-            throw ConfigurationFailureException.fromTemplate("Cannot find rsync executable '%s'.", rsyncExecutable
-                    .getAbsoluteFile());
-        }
-        return rsyncExecutable;
-    }
-
-    private static File findSshExecutable(String sshExecutablePath)
-    {
-        final File sshExecutable;
-        if (sshExecutablePath != null)
-        {
-            if (sshExecutablePath.length() > 0)
-            {
-                sshExecutable = new File(sshExecutablePath);
-            } else
-            // Explicitly disable tunneling via ssh on the command line.
-            {
-                sshExecutable = null;
-            }
-        } else
-        {
-            sshExecutable = OSUtilities.findExecutable("ssh");
-        }
-        if (sshExecutable != null && OSUtilities.executableExists(sshExecutable) == false)
-        {
-            throw ConfigurationFailureException.fromTemplate("Cannot find ssh executable '%s'.", sshExecutable
-                    .getAbsoluteFile());
-        }
-        return sshExecutable;
-    }
-
-    private static IPathCopier getPathCopier(Parameters parameters, File destinationDirectory)
-    {
-        IPathCopier copyProcess = suggestPathCopier(parameters, false);
-        boolean requiresDeletionBeforeCreation =
-                SelfTest.requiresDeletionBeforeCreation(copyProcess, destinationDirectory);
-        return suggestPathCopier(parameters, requiresDeletionBeforeCreation);
-    }
-
-    private static IFileSysOperationsFactory createFileSysOperations(final Parameters parameters)
-    {
-        final IReadPathOperations readAccessor = FileSystemHelper.createPathReadOperations();
-        return new IFileSysOperationsFactory()
-            {
-                public IPathCopier getCopier(File destinationDirectory)
-                {
-                    return getPathCopier(parameters, destinationDirectory);
-                }
-
-                public IPathRemover getRemover()
-                {
-                    return new FSPathRemover();
-                }
-
-                public IPathImmutableCopier getImmutableCopier()
-                {
-                    return getImmutablePathCopier(parameters);
-                }
-
-                public IReadPathOperations getReadAccessor()
-                {
-                    return readAccessor;
-                }
-            };
-    }
-
     /**
      * performs a self-test.
      */
@@ -223,7 +76,7 @@ public class Main
     {
         try
         {
-            IPathCopier copyProcess = suggestPathCopier(parameters, false);
+            IPathCopier copyProcess = new FileSysOparationsFactory(parameters).getCopierNoDeletionRequired();
             ArrayList<FileStore> stores = new ArrayList<FileStore>();
             stores.add(parameters.getIncomingStore());
             stores.add(parameters.getBufferStore());
@@ -250,14 +103,14 @@ public class Main
     /** exposed for testing purposes */
     static ITerminable startupServer(Parameters parameters, LocalBufferDirs bufferDirs)
     {
-        final IFileSysOperationsFactory factory = createFileSysOperations(parameters);
-        return MonitorStarter.start(parameters, factory, bufferDirs);
+        final IFileSysOperationsFactory factory = new FileSysOparationsFactory(parameters);
+        return DataMover.start(parameters, factory, bufferDirs);
     }
 
     private static void startupServer(Parameters parameters)
     {
-        final IFileSysOperationsFactory factory = createFileSysOperations(parameters);
-        MonitorStarter.start(parameters, factory);
+        final IFileSysOperationsFactory factory = new FileSysOparationsFactory(parameters);
+        DataMover.start(parameters, factory);
     }
 
     public static void main(String[] args)
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/MonitorStarter.java b/datamover/source/java/ch/systemsx/cisd/datamover/MonitorStarter.java
deleted file mode 100644
index 427fb9ca4ee..00000000000
--- a/datamover/source/java/ch/systemsx/cisd/datamover/MonitorStarter.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright 2007 ETH Zuerich, CISD
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * 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 ch.systemsx.cisd.datamover;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.Timer;
-
-import ch.systemsx.cisd.common.Constants;
-import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask;
-import ch.systemsx.cisd.common.utilities.ITerminable;
-import ch.systemsx.cisd.common.utilities.NamePrefixFileFilter;
-import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler;
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
-import ch.systemsx.cisd.datamover.helper.MarkerFile;
-import ch.systemsx.cisd.datamover.intf.IFileSysOperationsFactory;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
-import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
-import ch.systemsx.cisd.datamover.intf.IPathRemover;
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
-
-/**
- * A class that starts up the processing pipeline and its monitoring, based on the parameters provided.
- * 
- * @author Bernd Rinn
- * @author Tomasz Pylak on Aug 24, 2007
- */
-public class MonitorStarter
-{
-    private final static String LOCAL_COPY_IN_PROGRESS_DIR = "copy-in-progress";
-
-    private final static String LOCAL_COPY_COMPLETE_DIR = "copy-complete";
-
-    private final static String LOCAL_READY_TO_MOVE_DIR = "ready-to-move";
-
-    private final static String LOCAL_TEMP_DIR = "tmp";
-
-    private final Parameters parameters;
-
-    private final IFileSysOperationsFactory factory;
-
-    private final LocalBufferDirs bufferDirs;
-
-    /**
-     * starts the process of moving data and monitoring it
-     * 
-     * @return object which can be used to terminate the process and all its threads
-     */
-    public static final ITerminable start(Parameters parameters, IFileSysOperationsFactory factory)
-    {
-        LocalBufferDirs localBufferDirs =
-                new LocalBufferDirs(parameters, LOCAL_COPY_IN_PROGRESS_DIR, LOCAL_COPY_COMPLETE_DIR,
-                        LOCAL_READY_TO_MOVE_DIR, LOCAL_TEMP_DIR);
-        return start(parameters, factory, localBufferDirs);
-    }
-
-    /** Allows to specify buffer directories. Exposed for testing purposes. */
-    public static final ITerminable start(Parameters parameters, IFileSysOperationsFactory factory,
-            LocalBufferDirs localBufferDirs)
-    {
-        return new MonitorStarter(parameters, factory, localBufferDirs).start();
-    }
-
-    private MonitorStarter(Parameters parameters, IFileSysOperationsFactory factory, LocalBufferDirs bufferDirs)
-    {
-        this.parameters = parameters;
-        this.factory = factory;
-        this.bufferDirs = bufferDirs;
-    }
-
-    private ITerminable start()
-    {
-        final LazyPathHandler outgoingProcessor = startupOutgoingMovingProcess(parameters.getOutgoingStore());
-        final LazyPathHandler localProcessor = startupLocalProcessing(outgoingProcessor);
-        final Timer incomingProcessor = startupIncomingMovingProcess(parameters.getIncomingStore(), localProcessor);
-        return createTerminable(outgoingProcessor, localProcessor, incomingProcessor);
-    }
-
-    private static ITerminable createTerminable(final LazyPathHandler outgoingProcessor,
-            final LazyPathHandler localProcessor, final Timer incomingProcessor)
-    {
-        return new ITerminable()
-            {
-                public boolean terminate()
-                {
-                    incomingProcessor.cancel();
-                    boolean ok = localProcessor.terminate();
-                    ok = ok && outgoingProcessor.terminate();
-                    return ok;
-                }
-            };
-    }
-
-    private LazyPathHandler startupLocalProcessing(LazyPathHandler outgoingHandler)
-    {
-        final IPathImmutableCopier copier = factory.getImmutableCopier();
-        final IPathHandler localProcesingHandler =
-                LocalProcessorHandler.createAndRecover(parameters, bufferDirs.getCopyCompleteDir(), bufferDirs
-                        .getReadyToMoveDir(), bufferDirs.getTempDir(), outgoingHandler, copier);
-        return LazyPathHandler.create(localProcesingHandler, "Local Processor");
-    }
-
-    // --- Incoming data processing -----------------------
-
-    private Timer startupIncomingMovingProcess(FileStore incomingStore, LazyPathHandler localProcessor)
-    {
-        final IReadPathOperations readOperations = factory.getReadAccessor();
-        final boolean isIncomingRemote = parameters.getTreatIncomingAsRemote();
-
-        recoverIncomingAfterShutdown(incomingStore, readOperations, isIncomingRemote, localProcessor);
-        IPathHandler pathHandler =
-                createIncomingMovingPathHandler(incomingStore.getHost(), localProcessor, isIncomingRemote);
-        FileFilter filter = createQuitePeriodFilter(readOperations);
-
-        final DirectoryScanningTimerTask movingTask =
-                new DirectoryScanningTimerTask(incomingStore.getPath(), filter, pathHandler);
-        final Timer movingTimer = new Timer("Mover of Incomming Data");
-        // The moving task is scheduled at fixed rate. It makes sense especially if the task is moving data from the
-        // remote share. The rationale behind this is that if new items are
-        // added to the source directory while the remote timer task has been running for a long time, busy moving data,
-        // the task shoulnd't sit idle for the check time when there is actually work to do.
-        movingTimer.scheduleAtFixedRate(movingTask, 0, parameters.getCheckIntervalMillis());
-        return movingTimer;
-    }
-
-    private FileFilter createQuitePeriodFilter(final IReadPathOperations readOperations)
-    {
-        FileFilter quitePeriodFilter = new QuietPeriodFileFilter(parameters, readOperations);
-        FileFilter filterDeletionMarkers = new NamePrefixFileFilter(Constants.DELETION_IN_PROGRESS_PREFIX, false);
-        FileFilter filter = combineFilters(filterDeletionMarkers, quitePeriodFilter);
-        return filter;
-    }
-
-    private static FileFilter combineFilters(final FileFilter filter1, final FileFilter filter2)
-    {
-        return new FileFilter()
-            {
-                public boolean accept(File pathname)
-                {
-                    return filter1.accept(pathname) && filter2.accept(pathname);
-                }
-            };
-    }
-
-    private void recoverIncomingAfterShutdown(FileStore incomingStore, IReadPathOperations incomingReadOperations,
-            boolean isIncomingRemote, LazyPathHandler localProcessor)
-    {
-        if (isIncomingRemote)
-        {
-            recoverIncomingInProgress(incomingStore, incomingReadOperations, bufferDirs.getCopyInProgressDir(),
-                    bufferDirs.getCopyCompleteDir(), parameters.getPrefixForIncoming());
-        }
-        recoverIncomingCopyComplete(bufferDirs.getCopyCompleteDir(), localProcessor);
-    }
-
-    private static void recoverIncomingInProgress(FileStore incomingStore, IReadPathOperations incomingReadOperations,
-            File copyInProgressDir, File copyCompleteDir, String prefixTemplate)
-    {
-        final File[] files = FileSystemHelper.listFiles(copyInProgressDir);
-        if (files == null || files.length == 0)
-        {
-            return; // directory is empty, no recovery is needed
-        }
-
-        for (File file : files)
-        {
-            if (MarkerFile.isDeletionInProgressMarker(file))
-            {
-                continue;
-            }
-            recoverIncomingAfterShutdown(file, incomingStore, incomingReadOperations, copyCompleteDir, prefixTemplate);
-        }
-    }
-
-    private static void recoverIncomingAfterShutdown(File unfinishedFile, FileStore incomingStore,
-            IReadPathOperations incomingReadOperations, File copyCompleteDir, String prefixTemplate)
-    {
-        if (MarkerFile.isCopyFinishedMarker(unfinishedFile))
-        {
-            final File markerFile = unfinishedFile;
-            final File localCopy = MarkerFile.extractOriginalFromCopyFinishedMarker(markerFile);
-            if (localCopy.exists())
-            {
-                // copy and marker exist - do nothing, recovery will be done for copied resource
-            } else
-            {
-                // copy finished, resource moved, but marker was not deleted
-                markerFile.delete();
-            }
-        } else
-        // handle local copy
-        {
-            final File localCopy = unfinishedFile;
-            final File markerFile = MarkerFile.createCopyFinishedMarker(localCopy);
-            if (markerFile.exists())
-            {
-                // copy and marker exist - copy finished, but copied resource not moved
-                tryMoveFromInProgressToFinished(localCopy, markerFile, copyCompleteDir, prefixTemplate);
-            } else
-            // no marker
-            {
-                File incomingDir = incomingStore.getPath();
-                File originalInIncoming = new File(incomingDir, localCopy.getName());
-                if (incomingReadOperations.exists(originalInIncoming))
-                {
-                    // partial copy - nothing to do, will be copied again
-                } else
-                {
-                    // move finished, but marker not created
-                    tryMoveFromInProgressToFinished(localCopy, null, copyCompleteDir, prefixTemplate);
-                }
-            }
-        }
-    }
-
-    // schedule processing of all resources which were previously copied
-    private static void recoverIncomingCopyComplete(File copyCompleteDir, LazyPathHandler localProcessor)
-    {
-        final File[] files = FileSystemHelper.listFiles(copyCompleteDir);
-        if (files == null || files.length == 0)
-        {
-            return; // directory is empty, no recovery is needed
-        }
-
-        for (File file : files)
-        {
-            localProcessor.handle(file);
-        }
-    }
-
-    private IPathHandler createIncomingMovingPathHandler(final String sourceHostOrNull,
-            final LazyPathHandler localProcessor, final boolean isIncomingRemote)
-    {
-        return new IPathHandler()
-            {
-                public boolean handle(File sourceFile)
-                {
-                    if (isIncomingRemote)
-                    {
-                        return moveFromRemoteIncoming(sourceFile, sourceHostOrNull, localProcessor);
-                    } else
-                    {
-                        return moveFromLocalIncoming(sourceFile, localProcessor);
-                    }
-                }
-            };
-    }
-
-    private boolean moveFromLocalIncoming(File source, LazyPathHandler localProcessor)
-    {
-        final File finalFile = tryMoveLocal(source, bufferDirs.getCopyCompleteDir(), parameters.getPrefixForIncoming());
-        if (finalFile == null)
-        {
-            return false;
-        }
-        return localProcessor.handle(finalFile);
-    }
-
-    private boolean moveFromRemoteIncoming(File source, String sourceHostOrNull, LazyPathHandler localProcessor)
-    {
-        // 1. move from incoming: copy, delete, create copy-finished-marker
-        final File copyInProgressDir = bufferDirs.getCopyInProgressDir();
-        final boolean ok = moveFromRemoteToLocal(source, sourceHostOrNull, copyInProgressDir);
-        if (ok == false)
-        {
-            return false;
-        }
-        FileSystemHelper.createDestinationPath(source, null, copyInProgressDir, parameters.getPrefixForIncoming());
-        final File copiedFile = new File(copyInProgressDir, source.getName());
-        assert copiedFile.exists() : copiedFile.getAbsolutePath();
-        final File markerFile = MarkerFile.createCopyFinishedMarker(copiedFile);
-        assert markerFile.exists() : markerFile.getAbsolutePath();
-
-        // 2. Move to final directory, delete marker
-        final File finalFile =
-                tryMoveFromInProgressToFinished(copiedFile, markerFile, bufferDirs.getCopyCompleteDir(), parameters
-                        .getPrefixForIncoming());
-        if (finalFile == null)
-        {
-            return false;
-        }
-
-        // 3. schedule local processing, always successful
-        localProcessor.handle(finalFile);
-        return true;
-    }
-
-    private static File tryMoveFromInProgressToFinished(File copiedFile, File markerFileOrNull, File copyCompleteDir,
-            String prefixTemplate)
-    {
-        final File finalFile = tryMoveLocal(copiedFile, copyCompleteDir, prefixTemplate);
-        if (finalFile != null)
-        {
-            if (markerFileOrNull != null)
-            {
-                markerFileOrNull.delete(); // process even if marker file could not be deleted
-            }
-            return finalFile;
-        } else
-        {
-            return null;
-        }
-    }
-
-    private boolean moveFromRemoteToLocal(File source, String sourceHostOrNull, File localDestDir)
-    {
-        return createRemotePathMover(sourceHostOrNull, localDestDir, null).handle(source);
-    }
-
-    private static File tryMoveLocal(File sourceFile, File destinationDir, String prefixTemplate)
-    {
-        return FileSystemHelper.tryMoveLocal(sourceFile, destinationDir, prefixTemplate);
-    }
-
-    // --------------------------
-
-    private LazyPathHandler startupOutgoingMovingProcess(FileStore outputDir)
-    {
-        final File outgoingDirectory = outputDir.getPath();
-        final String outgoingHost = outputDir.getHost();
-        final IPathHandler remoteMover = createRemotePathMover(null, outgoingDirectory, outgoingHost);
-        return LazyPathHandler.create(remoteMover, "Final Destination Mover");
-    }
-
-    private IPathHandler createRemotePathMover(String sourceHost, File destinationDirectory, String destinationHost)
-    {
-        final IPathCopier copier = factory.getCopier(destinationDirectory);
-        final CopyActivityMonitor monitor =
-                new CopyActivityMonitor(destinationDirectory, factory.getReadAccessor(), copier, parameters);
-        final IPathRemover remover = factory.getRemover();
-        return new RemotePathMover(destinationDirectory, destinationHost, monitor, remover, copier, sourceHost,
-                parameters);
-    }
-}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/Parameters.java b/datamover/source/java/ch/systemsx/cisd/datamover/Parameters.java
index c476e6d144b..5928562a4b8 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/Parameters.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/Parameters.java
@@ -38,14 +38,16 @@ import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.BuildAndEnvironmentInfo;
 import ch.systemsx.cisd.common.utilities.IExitHandler;
 import ch.systemsx.cisd.common.utilities.SystemExit;
+import ch.systemsx.cisd.datamover.intf.IFileSysParameters;
 import ch.systemsx.cisd.datamover.intf.ITimingParameters;
+import ch.systemsx.cisd.datamover.utils.FileStore;
 
 /**
  * The class to process the command line parameters.
  * 
  * @author Bernd Rinn
  */
-public class Parameters implements ITimingParameters
+public class Parameters implements ITimingParameters, IFileSysParameters
 {
 
     private static final String SERVICE_PROPERTIES_FILE = "etc/service.properties";
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/SelfTest.java b/datamover/source/java/ch/systemsx/cisd/datamover/SelfTest.java
index 6fc42582dd7..3feabacdd41 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/SelfTest.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/SelfTest.java
@@ -16,21 +16,18 @@
 
 package ch.systemsx.cisd.datamover;
 
-import java.io.File;
-import java.io.IOException;
-
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 
 import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
 import ch.systemsx.cisd.common.exceptions.HighLevelException;
-import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.utils.FileStore;
 
 /**
  * A class that can perform a self test of the data mover.
@@ -39,9 +36,6 @@ import ch.systemsx.cisd.datamover.intf.IPathCopier;
  */
 public class SelfTest
 {
-
-    private static final Logger machineLog = LogFactory.getLogger(LogCategory.MACHINE, SelfTest.class);
-
     private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, SelfTest.class);
 
     static
@@ -163,58 +157,4 @@ public class SelfTest
             throw e;
         }
     }
-
-    /**
-     * @return <code>true</code> if the <var>copyProcess</var> on the file system where the <var>destinationDirectory</var>
-     *         resides requires deleting an existing file before it can be overwritten.
-     */
-    public static boolean requiresDeletionBeforeCreation(IPathCopier copyProcess, File destinationDirectory)
-    {
-        assert copyProcess != null;
-        assert destinationDirectory != null;
-        assert destinationDirectory.isDirectory();
-
-        String fileName = ".requiresDeletionBeforeCreation";
-        final File destinationFile = new File(destinationDirectory, fileName);
-        final File tmpSourceDir = new File(destinationDirectory, ".DataMover-OverrideTest");
-        final File sourceFile = new File(tmpSourceDir, fileName);
-        try
-        {
-            tmpSourceDir.mkdir();
-            sourceFile.createNewFile();
-            destinationFile.createNewFile();
-            // If we have e.g. a Cellera NAS server, the next call will raise an IOException.
-            final boolean OK = Status.OK.equals(copyProcess.copy(sourceFile, destinationDirectory));
-            if (machineLog.isInfoEnabled())
-            {
-                if (OK)
-                {
-                    machineLog.info(String.format("Copier %s on directory '%s' works with overwriting existing files.",
-                            copyProcess.getClass().getSimpleName(), destinationDirectory.getAbsolutePath()));
-                } else
-                {
-                    machineLog.info(String.format(
-                            "Copier %s on directory '%s' requires deletion before creation of existing files.",
-                            copyProcess.getClass().getSimpleName(), destinationDirectory.getAbsolutePath()));
-                }
-            }
-            return (OK == false);
-        } catch (IOException e)
-        {
-            if (machineLog.isInfoEnabled())
-            {
-                machineLog.info(String.format(
-                        "The file system on '%s' requires deletion before creation of existing files.",
-                        destinationDirectory.getAbsolutePath()));
-            }
-            return true;
-        } finally
-        {
-            // We don't check for success because there is nothing we can do if we fail.
-            sourceFile.delete();
-            tmpSourceDir.delete();
-            destinationFile.delete();
-        }
-    }
-
 }
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOparationsFactory.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOparationsFactory.java
new file mode 100644
index 00000000000..1efb8c07710
--- /dev/null
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/FileSysOparationsFactory.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 ch.systemsx.cisd.datamover.filesystem;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
+import ch.systemsx.cisd.common.exceptions.Status;
+import ch.systemsx.cisd.common.exceptions.StatusFlag;
+import ch.systemsx.cisd.common.logging.ISimpleLogger;
+import ch.systemsx.cisd.common.logging.LogCategory;
+import ch.systemsx.cisd.common.logging.LogFactory;
+import ch.systemsx.cisd.common.utilities.FileUtilities;
+import ch.systemsx.cisd.common.utilities.OSUtilities;
+import ch.systemsx.cisd.datamover.filesystem.impl.RecursiveHardLinkMaker;
+import ch.systemsx.cisd.datamover.filesystem.intf.IFileSysOperationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathRemover;
+import ch.systemsx.cisd.datamover.filesystem.intf.IReadPathOperations;
+import ch.systemsx.cisd.datamover.filesystem.remote.XcopyCopier;
+import ch.systemsx.cisd.datamover.filesystem.remote.rsync.RsyncCopier;
+import ch.systemsx.cisd.datamover.intf.IFileSysParameters;
+
+/**
+ * @author Tomasz Pylak on Sep 7, 2007
+ */
+public class FileSysOparationsFactory implements IFileSysOperationsFactory
+{
+    private static final Logger notificationLog =
+            LogFactory.getLogger(LogCategory.NOTIFY, FileSysOparationsFactory.class);
+
+    private static final Logger machineLog = LogFactory.getLogger(LogCategory.MACHINE, FileSysOparationsFactory.class);
+
+    final private IFileSysParameters parameters;
+
+    public FileSysOparationsFactory(IFileSysParameters parameters)
+    {
+        this.parameters = parameters;
+    }
+
+    public IPathRemover getRemover()
+    {
+        return new IPathRemover()
+            {
+                private final Status STATUS_FAILED_DELETION =
+                        new Status(StatusFlag.FATAL_ERROR, "Failed to remove path.");
+
+                public Status remove(File path)
+                {
+                    final boolean deletionOK = FileUtilities.deleteRecursively(path);
+                    return deletionOK ? Status.OK : STATUS_FAILED_DELETION;
+                }
+            };
+    }
+
+    public static IReadPathOperations getReadOperations()
+    {
+        return new IReadPathOperations()
+            {
+
+                public boolean exists(File file)
+                {
+                    return file.exists();
+                }
+
+                public long lastChanged(File path)
+                {
+                    return FileUtilities.lastChanged(path);
+                }
+
+                public File[] listFiles(File directory, FileFilter filter, ISimpleLogger loggerOrNull)
+                {
+                    return FileUtilities.listFiles(directory, filter, loggerOrNull);
+                }
+
+                public File[] listFiles(File directory, ISimpleLogger logger)
+                {
+                    return LocalFileSystem.listFiles(directory);
+                }
+            };
+    }
+
+    public IReadPathOperations getReadAccessor()
+    {
+        return getReadOperations();
+    }
+
+    public IPathImmutableCopier getImmutableCopier()
+    {
+        String lnExec = parameters.getHardLinkExecutable();
+        if (lnExec != null)
+        {
+            return RecursiveHardLinkMaker.create(lnExec);
+        }
+
+        IPathImmutableCopier copier = null;
+        if (OSUtilities.isWindows() == false)
+        {
+            copier = RecursiveHardLinkMaker.tryCreate();
+            if (copier != null)
+            {
+                return copier;
+            }
+        }
+        return createFakedImmCopier();
+    }
+
+    private IPathImmutableCopier createFakedImmCopier()
+    {
+        final IPathCopier normalCopier = suggestPathCopier(false);
+        return new IPathImmutableCopier()
+            {
+                public File tryCopy(File file, File destinationDirectory)
+                {
+                    Status status = normalCopier.copy(file, destinationDirectory);
+                    if (StatusFlag.OK.equals(status.getFlag()))
+                    {
+                        return new File(destinationDirectory, file.getName());
+                    } else
+                    {
+                        notificationLog.error(String.format("Copy of '%s' to '%s' failed: %s.", file.getPath(),
+                                destinationDirectory.getPath(), status));
+                        return null;
+                    }
+                }
+            };
+    }
+
+    private IPathCopier suggestPathCopier(boolean requiresDeletionBeforeCreation)
+    {
+        final File rsyncExecutable = findRsyncExecutable(parameters.getRsyncExecutable());
+        final File sshExecutable = findSshExecutable(parameters.getSshExecutable());
+        if (rsyncExecutable != null)
+        {
+            return new RsyncCopier(rsyncExecutable, sshExecutable, requiresDeletionBeforeCreation);
+        } else if (OSUtilities.isWindows())
+        {
+            return new XcopyCopier(OSUtilities.findExecutable("xcopy"), requiresDeletionBeforeCreation);
+        } else
+        {
+            throw new ConfigurationFailureException("Unable to find a copy engine.");
+        }
+    }
+
+    private static File findRsyncExecutable(final String rsyncExecutablePath)
+    {
+        final File rsyncExecutable;
+        if (rsyncExecutablePath != null)
+        {
+            rsyncExecutable = new File(rsyncExecutablePath);
+        } else if (OSUtilities.isWindows() == false)
+        {
+            rsyncExecutable = OSUtilities.findExecutable("rsync");
+        } else
+        {
+            rsyncExecutable = null;
+        }
+        if (rsyncExecutable != null && OSUtilities.executableExists(rsyncExecutable) == false)
+        {
+            throw ConfigurationFailureException.fromTemplate("Cannot find rsync executable '%s'.", rsyncExecutable
+                    .getAbsoluteFile());
+        }
+        return rsyncExecutable;
+    }
+
+    private static File findSshExecutable(String sshExecutablePath)
+    {
+        final File sshExecutable;
+        if (sshExecutablePath != null)
+        {
+            if (sshExecutablePath.length() > 0)
+            {
+                sshExecutable = new File(sshExecutablePath);
+            } else
+            // Explicitly disable tunneling via ssh on the command line.
+            {
+                sshExecutable = null;
+            }
+        } else
+        {
+            sshExecutable = OSUtilities.findExecutable("ssh");
+        }
+        if (sshExecutable != null && OSUtilities.executableExists(sshExecutable) == false)
+        {
+            throw ConfigurationFailureException.fromTemplate("Cannot find ssh executable '%s'.", sshExecutable
+                    .getAbsoluteFile());
+        }
+        return sshExecutable;
+    }
+
+    /**
+     * @return <code>true</code> if the <var>copyProcess</var> on the file system where the <var>destinationDirectory</var>
+     *         resides requires deleting an existing file before it can be overwritten.
+     */
+    private static boolean requiresDeletionBeforeCreation(IPathCopier copyProcess, File destinationDirectory)
+    {
+        assert copyProcess != null;
+        assert destinationDirectory != null;
+        assert destinationDirectory.isDirectory();
+
+        String fileName = ".requiresDeletionBeforeCreation";
+        final File destinationFile = new File(destinationDirectory, fileName);
+        final File tmpSourceDir = new File(destinationDirectory, ".DataMover-OverrideTest");
+        final File sourceFile = new File(tmpSourceDir, fileName);
+        try
+        {
+            tmpSourceDir.mkdir();
+            sourceFile.createNewFile();
+            destinationFile.createNewFile();
+            // If we have e.g. a Cellera NAS server, the next call will raise an IOException.
+            final boolean OK = Status.OK.equals(copyProcess.copy(sourceFile, destinationDirectory));
+            if (machineLog.isInfoEnabled())
+            {
+                if (OK)
+                {
+                    machineLog.info(String.format("Copier %s on directory '%s' works with overwriting existing files.",
+                            copyProcess.getClass().getSimpleName(), destinationDirectory.getAbsolutePath()));
+                } else
+                {
+                    machineLog.info(String.format(
+                            "Copier %s on directory '%s' requires deletion before creation of existing files.",
+                            copyProcess.getClass().getSimpleName(), destinationDirectory.getAbsolutePath()));
+                }
+            }
+            return (OK == false);
+        } catch (IOException e)
+        {
+            if (machineLog.isInfoEnabled())
+            {
+                machineLog.info(String.format(
+                        "The file system on '%s' requires deletion before creation of existing files.",
+                        destinationDirectory.getAbsolutePath()));
+            }
+            return true;
+        } finally
+        {
+            // We don't check for success because there is nothing we can do if we fail.
+            sourceFile.delete();
+            tmpSourceDir.delete();
+            destinationFile.delete();
+        }
+    }
+
+    public IPathCopier getCopierNoDeletionRequired()
+    {
+        return suggestPathCopier(false);
+    }
+
+    public IPathCopier getCopier(File destinationDirectory)
+    {
+        IPathCopier copyProcess = suggestPathCopier(false);
+        if (requiresDeletionBeforeCreation(copyProcess, destinationDirectory))
+        {
+            return suggestPathCopier(true);
+        } else
+        {
+            return copyProcess;
+        }
+    }
+}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/helper/FileSystemHelper.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/LocalFileSystem.java
similarity index 71%
rename from datamover/source/java/ch/systemsx/cisd/datamover/helper/FileSystemHelper.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/LocalFileSystem.java
index cfcde8a366f..9f22f4b4078 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/helper/FileSystemHelper.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/LocalFileSystem.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.helper;
+package ch.systemsx.cisd.datamover.filesystem;
 
 import java.io.File;
 import java.io.FileFilter;
@@ -30,18 +30,17 @@ import ch.systemsx.cisd.common.logging.Log4jSimpleLogger;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.IntraFSPathMover;
-import ch.systemsx.cisd.datamover.LocalProcessorHandler;
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
 
 /**
  * Basic file system operations helper.
  * 
  * @author Tomasz Pylak on Aug 27, 2007
  */
-public class FileSystemHelper
+public class LocalFileSystem
 {
-    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, LocalProcessorHandler.class);
+    private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, LocalFileSystem.class);
+
+    private static final Logger notificationLog = LogFactory.getLogger(LogCategory.NOTIFY, LocalFileSystem.class);
 
     public static File ensureDirectoryExists(File dir, String newDirName)
     {
@@ -73,33 +72,6 @@ public class FileSystemHelper
         return FileUtilities.listFiles(directory, acceptAll, errorLogger);
     }
 
-    public static IReadPathOperations createPathReadOperations()
-    {
-        return new IReadPathOperations()
-            {
-
-                public boolean exists(File file)
-                {
-                    return file.exists();
-                }
-
-                public long lastChanged(File path)
-                {
-                    return FileUtilities.lastChanged(path);
-                }
-
-                public File[] listFiles(File directory, FileFilter filter, ISimpleLogger loggerOrNull)
-                {
-                    return FileUtilities.listFiles(directory, filter, loggerOrNull);
-                }
-
-                public File[] listFiles(File directory, ISimpleLogger logger)
-                {
-                    return FileSystemHelper.listFiles(directory);
-                }
-            };
-    }
-
     /**
      * Moves source file to destination directory.
      */
@@ -112,14 +84,30 @@ public class FileSystemHelper
      * Moves source file to destination directory, putting <var>prefixTemplate</var> in front of its name after
      * replacing '%t' with the current time stamp.
      */
-    public static File tryMoveLocal(File sourceFile, File destinationDirectory, String prefixTemplate)
+    public static File tryMoveLocal(File sourcePath, File destinationDirectory, String prefixTemplate)
     {
-        boolean ok = new IntraFSPathMover(destinationDirectory, prefixTemplate).handle(sourceFile);
-        if (ok == false)
+        assert destinationDirectory != null;
+        assert FileUtilities.checkDirectoryFullyAccessible(destinationDirectory, "destination") == null : "Directory is not fully accessible "
+                + destinationDirectory.getAbsolutePath();
+        assert prefixTemplate != null;
+        assert sourcePath != null;
+
+        final String destinationPath = createDestinationPath(sourcePath, null, destinationDirectory, prefixTemplate);
+        if (operationLog.isInfoEnabled())
+        {
+            operationLog.info(String.format("Moving path '%s' to '%s'", sourcePath.getPath(), destinationPath));
+        }
+        File destFile = new File(destinationPath);
+        boolean movedOK = sourcePath.renameTo(destFile);
+        if (movedOK == false)
         {
+            notificationLog.error(String.format("Moving path '%s' to directory '%s' failed.", sourcePath,
+                    destinationDirectory));
             return null;
+        } else
+        {
+            return destFile;
         }
-        return new File(createDestinationPath(sourceFile, null, destinationDirectory, prefixTemplate));
     }
 
     /**
@@ -127,7 +115,7 @@ public class FileSystemHelper
      * defined by <var>prefixTemplate</var>. Note that '%t' in <var>prefixTemplate</var> will be replaced by the
      * current time stamp in format YYYYmmddhhMMss.
      */
-    public static String createDestinationPath(File sourcePath, String destinationHostOrNull,
+    private static String createDestinationPath(File sourcePath, String destinationHostOrNull,
             File destinationDirectory, String prefixTemplate)
     {
         assert sourcePath != null;
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RemoteMonitoredMoverFactory.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RemoteMonitoredMoverFactory.java
new file mode 100644
index 00000000000..3c42aac9c0d
--- /dev/null
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/RemoteMonitoredMoverFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 ch.systemsx.cisd.datamover.filesystem;
+
+import java.io.File;
+
+import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask.IPathHandler;
+import ch.systemsx.cisd.datamover.filesystem.intf.IFileSysOperationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathRemover;
+import ch.systemsx.cisd.datamover.filesystem.remote.CopyActivityMonitor;
+import ch.systemsx.cisd.datamover.filesystem.remote.RemotePathMover;
+import ch.systemsx.cisd.datamover.intf.ITimingParameters;
+
+/**
+ * @author Tomasz Pylak on Sep 7, 2007
+ */
+public class RemoteMonitoredMoverFactory
+{
+    /**
+     * Creates a handler to move files remotely and monitor the progress
+     * 
+     * @param sourceHost The host to move paths from, or <code>null</code>, if data will be moved from the local file
+     *            system
+     * @param destinationDirectory The directory to move paths to.
+     * @param destinationHost The host to move paths to, or <code>null</code>, if <var>destinationDirectory</var> is
+     *            a remote share.
+     * @param fsFactory operations on (remote) file system
+     * @param parameters The timing parameters used for monitoring and reporting stall situations.
+     */
+    public static final IPathHandler create(String sourceHost, File destinationDirectory, String destinationHost,
+            IFileSysOperationsFactory fsFactory, ITimingParameters parameters)
+    {
+        final IPathCopier copier = fsFactory.getCopier(destinationDirectory);
+        final CopyActivityMonitor monitor =
+                new CopyActivityMonitor(destinationDirectory, fsFactory.getReadAccessor(), copier, parameters);
+        final IPathRemover remover = fsFactory.getRemover();
+        return new RemotePathMover(destinationDirectory, destinationHost, monitor, remover, copier, sourceHost,
+                parameters);
+    }
+}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/helper/CmdLineHelper.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/common/CmdLineHelper.java
similarity index 98%
rename from datamover/source/java/ch/systemsx/cisd/datamover/helper/CmdLineHelper.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/common/CmdLineHelper.java
index f517cc31a4c..203b53265f4 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/helper/CmdLineHelper.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/common/CmdLineHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.helper;
+package ch.systemsx.cisd.datamover.filesystem.common;
 
 import java.io.BufferedReader;
 import java.io.IOException;
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMaker.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMaker.java
similarity index 96%
rename from datamover/source/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMaker.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMaker.java
index b6447b531eb..e92e5b4f937 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMaker.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMaker.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.hardlink;
+package ch.systemsx.cisd.datamover.filesystem.impl;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -25,8 +25,8 @@ import org.apache.log4j.Logger;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
-import ch.systemsx.cisd.datamover.helper.CmdLineHelper;
-import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.filesystem.common.CmdLineHelper;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathImmutableCopier;
 
 /**
  * Utility to create a hard link of a file or copy recursively a directories structure, creating a hard link for each
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysOperationsFactory.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IFileSysOperationsFactory.java
similarity index 95%
rename from datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysOperationsFactory.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IFileSysOperationsFactory.java
index 88702d5f13d..a25d7239e27 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysOperationsFactory.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IFileSysOperationsFactory.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package ch.systemsx.cisd.datamover.intf;
+package ch.systemsx.cisd.datamover.filesystem.intf;
 
 import java.io.File;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathCopier.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathCopier.java
similarity index 98%
rename from datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathCopier.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathCopier.java
index 245b5d473f7..bfd3c493828 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathCopier.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathCopier.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.intf;
+package ch.systemsx.cisd.datamover.filesystem.intf;
 
 import java.io.File;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathImmutableCopier.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathImmutableCopier.java
similarity index 95%
rename from datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathImmutableCopier.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathImmutableCopier.java
index aeddc25649a..cafc75def23 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathImmutableCopier.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathImmutableCopier.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.intf;
+package ch.systemsx.cisd.datamover.filesystem.intf;
 
 import java.io.File;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathRemover.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathRemover.java
similarity index 96%
rename from datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathRemover.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathRemover.java
index a79ae7e9bbf..3cbe79051b0 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IPathRemover.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IPathRemover.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.intf;
+package ch.systemsx.cisd.datamover.filesystem.intf;
 
 import java.io.File;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IReadPathOperations.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IReadPathOperations.java
similarity index 96%
rename from datamover/source/java/ch/systemsx/cisd/datamover/intf/IReadPathOperations.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IReadPathOperations.java
index 424787482db..e7393468ed7 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IReadPathOperations.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/intf/IReadPathOperations.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.intf;
+package ch.systemsx.cisd.datamover.filesystem.intf;
 
 import java.io.File;
 import java.io.FileFilter;
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/CopyActivityMonitor.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitor.java
similarity index 99%
rename from datamover/source/java/ch/systemsx/cisd/datamover/CopyActivityMonitor.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitor.java
index 36966f370e9..1c465603619 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/CopyActivityMonitor.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.filesystem.remote;
 
 import java.io.File;
 import java.util.Timer;
@@ -28,7 +28,7 @@ import org.apache.log4j.Logger;
 import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.ITerminable;
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
+import ch.systemsx.cisd.datamover.filesystem.intf.IReadPathOperations;
 import ch.systemsx.cisd.datamover.intf.ITimingParameters;
 
 /**
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/RemotePathMover.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
similarity index 97%
rename from datamover/source/java/ch/systemsx/cisd/datamover/RemotePathMover.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
index 2770ffde07f..ed8f69a32f6 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/RemotePathMover.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/RemotePathMover.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.filesystem.remote;
 
 import java.io.File;
 import java.io.IOException;
@@ -27,10 +27,10 @@ import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.DirectoryScanningTimerTask;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.helper.MarkerFile;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
-import ch.systemsx.cisd.datamover.intf.IPathRemover;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathRemover;
 import ch.systemsx.cisd.datamover.intf.ITimingParameters;
+import ch.systemsx.cisd.datamover.utils.MarkerFile;
 
 /**
  * A class that moves files and directories to remote directories. This class monitors the copy process and, if
@@ -254,7 +254,8 @@ public final class RemotePathMover implements DirectoryScanningTimerTask.IPathHa
 
     private boolean markAsFinished(File path)
     {
-        final File markFinishedFile = new File(destinationDirectory, MarkerFile.getCopyFinishedMarkerName(path.getName()));
+        final File markFinishedFile =
+                new File(destinationDirectory, MarkerFile.getCopyFinishedMarkerName(path.getName()));
         if (destinationHost == null)
         {
             return markLocal(markFinishedFile);
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/xcopy/XcopyCopier.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/XcopyCopier.java
similarity index 98%
rename from datamover/source/java/ch/systemsx/cisd/datamover/xcopy/XcopyCopier.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/XcopyCopier.java
index 6f9e5f32028..71489fab395 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/xcopy/XcopyCopier.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/XcopyCopier.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.xcopy;
+package ch.systemsx.cisd.datamover.filesystem.remote;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -34,8 +34,8 @@ import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
-import ch.systemsx.cisd.datamover.helper.CmdLineHelper;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.common.CmdLineHelper;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
 
 /**
  * A class that encapsulates the <code>xcopy</code> call for doing an archive copy.
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncCopier.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopier.java
similarity index 97%
rename from datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncCopier.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopier.java
index c46e1341066..34d2594fc32 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncCopier.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopier.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.rsync;
+package ch.systemsx.cisd.datamover.filesystem.remote.rsync;
 
-import static ch.systemsx.cisd.datamover.rsync.RsyncVersionChecker.getVersion;
+import static ch.systemsx.cisd.datamover.filesystem.remote.rsync.RsyncVersionChecker.getVersion;
 
 import java.io.File;
 import java.io.IOException;
@@ -36,9 +36,9 @@ import ch.systemsx.cisd.common.logging.LogCategory;
 import ch.systemsx.cisd.common.logging.LogFactory;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 import ch.systemsx.cisd.common.utilities.OSUtilities;
-import ch.systemsx.cisd.datamover.helper.CmdLineHelper;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
-import ch.systemsx.cisd.datamover.rsync.RsyncVersionChecker.RsyncVersion;
+import ch.systemsx.cisd.datamover.filesystem.common.CmdLineHelper;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.remote.rsync.RsyncVersionChecker.RsyncVersion;
 
 /**
  * A class that encapsulates the <code>rsync</code> call for doing an archive copy.
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncExitValueTranslator.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncExitValueTranslator.java
similarity index 98%
rename from datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncExitValueTranslator.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncExitValueTranslator.java
index 29fd64ff0ec..7d293079036 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncExitValueTranslator.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncExitValueTranslator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.rsync;
+package ch.systemsx.cisd.datamover.filesystem.remote.rsync;
 
 import ch.systemsx.cisd.common.exceptions.StatusFlag;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncVersionChecker.java b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncVersionChecker.java
similarity index 99%
rename from datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncVersionChecker.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncVersionChecker.java
index 8afe6495ae1..7cd888e0edb 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/rsync/RsyncVersionChecker.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncVersionChecker.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.rsync;
+package ch.systemsx.cisd.datamover.filesystem.remote.rsync;
 
 import java.io.BufferedReader;
 import java.io.IOException;
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysParameters.java b/datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysParameters.java
new file mode 100644
index 00000000000..d439e05b3f4
--- /dev/null
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/intf/IFileSysParameters.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 ch.systemsx.cisd.datamover.intf;
+
+/**
+ * @author Tomasz Pylak on Sep 7, 2007
+ */
+public interface IFileSysParameters
+{
+    /**
+     * The path to the <code>ln</code> executable file for creating hard links.
+     */
+    String getHardLinkExecutable();
+
+    /**
+     * The name of the <code>rsync</code> executable to use for copy operations.
+     */
+    String getRsyncExecutable();
+
+    /**
+     * The name of the <code>ssh</code> executable to use for creating tunnels.
+     */
+    String getSshExecutable();
+}
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/FileStore.java b/datamover/source/java/ch/systemsx/cisd/datamover/utils/FileStore.java
similarity index 95%
rename from datamover/source/java/ch/systemsx/cisd/datamover/FileStore.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/utils/FileStore.java
index b6b6932b550..afb60e57afd 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/FileStore.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/utils/FileStore.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.utils;
 
 import java.io.File;
 import java.io.IOException;
@@ -38,7 +38,7 @@ public class FileStore
 
     private final boolean remote;
 
-    FileStore(File path, String kind, String host, boolean remote)
+    public FileStore(File path, String kind, String host, boolean remote)
     {
         this.path = path;
         this.kind = kind;
@@ -108,7 +108,7 @@ public class FileStore
      * Returns <code>true</code>, if the file store resides on a remote computer and <code>false</code> otherwise.
      * <p>
      * Note that this method can return <code>true</code> despite {@link #getHost()} returning <code>null</code>.
-     * In this case the file store is on a remote share mounted on local host via NFS or CIFS. 
+     * In this case the file store is on a remote share mounted on local host via NFS or CIFS.
      */
     public boolean isRemote()
     {
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/LazyPathHandler.java b/datamover/source/java/ch/systemsx/cisd/datamover/utils/LazyPathHandler.java
similarity index 98%
rename from datamover/source/java/ch/systemsx/cisd/datamover/LazyPathHandler.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/utils/LazyPathHandler.java
index 94b8750745f..e99308d2ab8 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/LazyPathHandler.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/utils/LazyPathHandler.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.utils;
 
 import java.io.File;
 import java.util.concurrent.BlockingQueue;
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/LocalBufferDirs.java b/datamover/source/java/ch/systemsx/cisd/datamover/utils/LocalBufferDirs.java
similarity index 76%
rename from datamover/source/java/ch/systemsx/cisd/datamover/LocalBufferDirs.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/utils/LocalBufferDirs.java
index e2223d594e9..1f817e67efb 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/LocalBufferDirs.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/utils/LocalBufferDirs.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.utils;
 
 import java.io.File;
 
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
+import ch.systemsx.cisd.datamover.Parameters;
+import ch.systemsx.cisd.datamover.filesystem.LocalFileSystem;
 
 /**
  * Paths to different local buffer directories.
@@ -38,10 +39,10 @@ public class LocalBufferDirs
             String readyToMoveDirName, String tempDirName)
     {
         File buffer = parameters.getBufferStore().getPath();
-        this.copyInProgressDir = FileSystemHelper.ensureDirectoryExists(buffer, copyInProgressDirName);
-        this.copyCompleteDir = FileSystemHelper.ensureDirectoryExists(buffer, copyCompleteDirName);
-        this.readyToMoveDir = FileSystemHelper.ensureDirectoryExists(buffer, readyToMoveDirName);
-        this.tempDir = FileSystemHelper.ensureDirectoryExists(buffer, tempDirName);
+        this.copyInProgressDir = LocalFileSystem.ensureDirectoryExists(buffer, copyInProgressDirName);
+        this.copyCompleteDir = LocalFileSystem.ensureDirectoryExists(buffer, copyCompleteDirName);
+        this.readyToMoveDir = LocalFileSystem.ensureDirectoryExists(buffer, readyToMoveDirName);
+        this.tempDir = LocalFileSystem.ensureDirectoryExists(buffer, tempDirName);
     }
 
     /** here data are copied from incoming */
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/helper/MarkerFile.java b/datamover/source/java/ch/systemsx/cisd/datamover/utils/MarkerFile.java
similarity index 97%
rename from datamover/source/java/ch/systemsx/cisd/datamover/helper/MarkerFile.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/utils/MarkerFile.java
index 28e7076673a..ffc0c4995f5 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/helper/MarkerFile.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/utils/MarkerFile.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.helper;
+package ch.systemsx.cisd.datamover.utils;
 
 import java.io.File;
 
diff --git a/datamover/source/java/ch/systemsx/cisd/datamover/QuietPeriodFileFilter.java b/datamover/source/java/ch/systemsx/cisd/datamover/utils/QuietPeriodFileFilter.java
similarity index 94%
rename from datamover/source/java/ch/systemsx/cisd/datamover/QuietPeriodFileFilter.java
rename to datamover/source/java/ch/systemsx/cisd/datamover/utils/QuietPeriodFileFilter.java
index 83423268ee2..4ef46b413f7 100644
--- a/datamover/source/java/ch/systemsx/cisd/datamover/QuietPeriodFileFilter.java
+++ b/datamover/source/java/ch/systemsx/cisd/datamover/utils/QuietPeriodFileFilter.java
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.utils;
 
 import java.io.File;
 import java.io.FileFilter;
 
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
+import ch.systemsx.cisd.datamover.filesystem.intf.IReadPathOperations;
 import ch.systemsx.cisd.datamover.intf.ITimingParameters;
 
 /**
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/MainTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/MainTest.java
index 2b07f11caf8..e5cb1454a65 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/MainTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/MainTest.java
@@ -34,6 +34,7 @@ import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
 import ch.systemsx.cisd.common.utilities.ITerminable;
 import ch.systemsx.cisd.datamover.testhelper.FileStructEngine;
+import ch.systemsx.cisd.datamover.utils.LocalBufferDirs;
 
 /**
  * @author Tomasz Pylak on Aug 29, 2007
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/SelfTestTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/SelfTestTest.java
index 2f7bd15f546..4bfc3d5c8d6 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/SelfTestTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/SelfTestTest.java
@@ -26,7 +26,8 @@ import ch.systemsx.cisd.common.exceptions.ConfigurationFailureException;
 import ch.systemsx.cisd.common.exceptions.Status;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathCopier;
+import ch.systemsx.cisd.datamover.utils.FileStore;
 
 /**
  * Test cases for the {@link SelfTest}.
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMakerTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMakerTest.java
similarity index 96%
rename from datamover/sourceTest/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMakerTest.java
rename to datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMakerTest.java
index 201a5f6c814..a46aa677872 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/hardlink/RecursiveHardLinkMakerTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/impl/RecursiveHardLinkMakerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.hardlink;
+package ch.systemsx.cisd.datamover.filesystem.impl;
 
 import static org.testng.AssertJUnit.assertEquals;
 
@@ -31,7 +31,8 @@ import org.testng.annotations.Test;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.CollectionIO;
 import ch.systemsx.cisd.common.utilities.FileUtilities;
-import ch.systemsx.cisd.datamover.intf.IPathImmutableCopier;
+import ch.systemsx.cisd.datamover.filesystem.impl.RecursiveHardLinkMaker;
+import ch.systemsx.cisd.datamover.filesystem.intf.IPathImmutableCopier;
 
 /**
  * Test cases for the {@link RecursiveHardLinkMaker}.
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/CopyActivityMonitorTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitorTest.java
similarity index 97%
rename from datamover/sourceTest/java/ch/systemsx/cisd/datamover/CopyActivityMonitorTest.java
rename to datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitorTest.java
index d9167bd1c4f..c2ec243407e 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/CopyActivityMonitorTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/CopyActivityMonitorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover;
+package ch.systemsx.cisd.datamover.filesystem.remote;
 
 import java.io.File;
 import java.io.FileFilter;
@@ -32,8 +32,8 @@ import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.logging.LogMonitoringAppender;
 import ch.systemsx.cisd.common.utilities.ITerminable;
 import ch.systemsx.cisd.common.utilities.StoringUncaughtExceptionHandler;
-import ch.systemsx.cisd.datamover.helper.FileSystemHelper;
-import ch.systemsx.cisd.datamover.intf.IReadPathOperations;
+import ch.systemsx.cisd.datamover.filesystem.FileSysOparationsFactory;
+import ch.systemsx.cisd.datamover.filesystem.intf.IReadPathOperations;
 import ch.systemsx.cisd.datamover.intf.ITimingParameters;
 
 /**
@@ -89,7 +89,7 @@ public class CopyActivityMonitorTest
 
         public ReadOperationsOriginalImpl()
         {
-            this.impl = FileSystemHelper.createPathReadOperations();
+            this.impl = FileSysOparationsFactory.getReadOperations();
         }
 
         public long lastChanged(File path)
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/rsync/RsyncCopierTest.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopierTest.java
similarity index 97%
rename from datamover/sourceTest/java/ch/systemsx/cisd/datamover/rsync/RsyncCopierTest.java
rename to datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopierTest.java
index 1d423b72097..144498707d0 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/rsync/RsyncCopierTest.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/filesystem/remote/rsync/RsyncCopierTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package ch.systemsx.cisd.datamover.rsync;
+package ch.systemsx.cisd.datamover.filesystem.remote.rsync;
 
 import static org.testng.AssertJUnit.assertEquals;
 
@@ -31,6 +31,7 @@ import ch.systemsx.cisd.common.exceptions.StatusFlag;
 import ch.systemsx.cisd.common.logging.LogInitializer;
 import ch.systemsx.cisd.common.utilities.CollectionIO;
 import ch.systemsx.cisd.common.utilities.StoringUncaughtExceptionHandler;
+import ch.systemsx.cisd.datamover.filesystem.remote.rsync.RsyncCopier;
 
 /**
  * Test cases for the {@link RsyncCopier} class.
diff --git a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/testhelper/FileStructEngine.java b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/testhelper/FileStructEngine.java
index 5a579a2f155..e564f182e23 100644
--- a/datamover/sourceTest/java/ch/systemsx/cisd/datamover/testhelper/FileStructEngine.java
+++ b/datamover/sourceTest/java/ch/systemsx/cisd/datamover/testhelper/FileStructEngine.java
@@ -25,7 +25,7 @@ import java.util.List;
 import static ch.systemsx.cisd.datamover.testhelper.FileSystemHelper.*;
 
 import ch.systemsx.cisd.common.utilities.CollectionIO;
-import ch.systemsx.cisd.datamover.helper.MarkerFile;
+import ch.systemsx.cisd.datamover.utils.MarkerFile;
 
 /**
  * Immutable helper for creating a sample directory structure and manipulating it.
-- 
GitLab