diff --git a/docs/unimelb-mf-sync.md b/docs/unimelb-mf-sync.md
deleted file mode 100644
index 85ef760a949028c2cbd56133dd524fdc56301d33..0000000000000000000000000000000000000000
--- a/docs/unimelb-mf-sync.md
+++ /dev/null
@@ -1,33 +0,0 @@
-```
-USAGE:
-    unimelb-mf-sync [OPTIONS] <dir1> <namespace1> [<dir2> <namespace2>]
-
-DESCRIPTION:
-    Synchronize local directory with remote asset namespace. The algorithm will ensure that the two end point sets of data are the same set, that is, the join of the two sets.  There are no deletions at either end
-
-OPTIONS:
-    --mf.config <mflux.cfg>                   Path to the config file that contains Mediaflux server details and user credentials.
-    --mf.host <host>                          Mediaflux server host.
-    --mf.port <port>                          Mediaflux server port.
-    --mf.transport <https|http|tcp/ip>        Mediaflux server transport, can be http, https or tcp/ip.
-    --mf.auth <domain,user,password>          Mediaflux user credentials.
-    --mf.token <token>                        Mediaflux secure identity token.
-    --csum-check                              Files are equated if the name and size are the same. In addition, with this argument, you can optionally compute the CRC32 checksumk to decide if two files are the same.
-    --nb-queriers <n>                         Number of query threads. Defaults to 1
-    --nb-workers <n>                          Number of concurrent worker threads to download data. Defaults to 1
-    --nb-retries <n>                          Retry times when error occurs. Defaults to 0
-    --batch-size <size>                       Size of the query result. Defaults to 1000
-    --daemon                                  Run as a daemon.
-    --daemon-port <port>                      Daemon listener port if running as a daemon. Defaults to 9761
-    --daemon-scan-interval <seconds>          Time interval (in seconds) between scans of source directories. Defaults to 60 seconds.
-    --log-dir <dir>                           Path to the directory for log files. No logging if not specified.
-    --notify <email-addresses>                When completes, send email notification to the recipients(comma-separated email addresses if multiple). Not applicable for daemon mode.
-    --quiet                                   Do not print progress messages.
-    --help                                    Prints usage.
-
-POSITIONAL ARGUMENTS:
-    <dir>                                     Local directory path.
-    <namespace>                               Corresponding remote asset namespace path.
-
-    unimelb-mf-sync --mf.config ~/.Arcitecta/mflux.cfg --nb-queriers 2 --nb-workers 4 ~/Documents/foo /projects/proj-1128.1.59/foo
-```
diff --git a/src/main/config/samples/unimelb-mf-clients-properties.xml b/src/main/config/samples/unimelb-mf-clients-properties.xml
index d0f23f9ab9a680a6713daedd06f83abf2cae8a02..e4831a6a54ae79476a64ad70877abfc06030dec7 100644
--- a/src/main/config/samples/unimelb-mf-clients-properties.xml
+++ b/src/main/config/samples/unimelb-mf-clients-properties.xml
@@ -40,7 +40,7 @@
 			<!-- Batch size for checking files with remote assets. Set to 1 will check 
 				files one by one, which will slow down significantly when there are large 
 				number of small files. -->
-			<batchSize>1000</batchatchSize>
+			<batchSize>1000</batchSize>
 			<!-- Compare CRC32 checksum after uploading -->
 			<csumCheck>true</csumCheck>
             <!-- Overwrite if destination file exists (when downloading)-->
@@ -83,7 +83,7 @@
 		<job action="check-upload">
 			<!-- source directory -->
 			<directory>/path/to/src-directory2</directory>
-            <namespace parent="true">/path/to/dst-parent-ns</namspace>
+            <namespace parent="true">/path/to/dst-parent-ns</namespace>
 		</job>
 		<job action="upload">
 			<!-- source directory -->
diff --git a/src/main/java/unimelb/mf/client/sync/MFSyncApp.java b/src/main/java/unimelb/mf/client/sync/MFSyncApp.java
index c2ee1d17583aebf595504a4393ea3d22d00585d8..6bcc78eec64750cbe7dd1625fbf36ea127bfdbea 100644
--- a/src/main/java/unimelb/mf/client/sync/MFSyncApp.java
+++ b/src/main/java/unimelb/mf/client/sync/MFSyncApp.java
@@ -208,7 +208,7 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
                 }, new ThreadPoolExecutor.CallerRunsPolicy());
 
         try {
-            
+
             session().startPingServerPeriodically(60000);
 
             // starts the daemon regardless whether is recurring execution or
@@ -355,15 +355,6 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
         List<Job> jobs = settings().jobs();
         if (jobs != null && !jobs.isEmpty()) {
             sb.append("\n");
-            if (settings().hasSyncJobs()) {
-                sb.append("Sync:\n");
-                for (Job job : jobs) {
-                    if (job.action() == Action.SYNC) {
-                        sb.append("    src(directory):" + job.directory().toString()).append("\n");
-                        sb.append("    dst(mediaflux):" + job.namespace()).append("\n");
-                    }
-                }
-            }
             if (settings().hasDownloadJobs()) {
                 sb.append("Download:\n");
                 for (Job job : jobs) {
@@ -397,11 +388,12 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
 
         sb.append("\n");
         int totalFiles = _nbUploadedFiles.get() + _nbSkippedFiles.get() + _nbFailedFiles.get();
-        if (totalFiles > 0) {            
+        if (totalFiles > 0) {
             if (_nbUploadedZeroSizeFiles.get() > 0) {
-                sb.append(String.format("    Uploaded files: %,32d files(%d zero-size)", _nbUploadedFiles.get(), _nbUploadedZeroSizeFiles.get())).append("\n");
+                sb.append(String.format("    Uploaded files: %,32d files(%d zero-size)", _nbUploadedFiles.get(),
+                        _nbUploadedZeroSizeFiles.get())).append("\n");
             } else {
-                sb.append(String.format("    Uploaded files: %,32d files", _nbUploadedFiles.get())).append("\n");    
+                sb.append(String.format("    Uploaded files: %,32d files", _nbUploadedFiles.get())).append("\n");
             }
             sb.append(String.format("     Skipped files: %,32d files", _nbSkippedFiles.get())).append("\n");
             sb.append(String.format("      Failed files: %,32d files", _nbFailedFiles.get())).append("\n");
@@ -421,7 +413,8 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
         int totalAssets = _nbDownloadedAssets.get() + _nbSkippedAssets.get() + _nbFailedAssets.get();
         if (totalAssets > 0) {
             if (_nbDownloadedZeroSizeAssets.get() > 0) {
-                sb.append(String.format(" Downloaded assets: %,32d files(%d zero-size)", _nbDownloadedAssets.get(), _nbDownloadedZeroSizeAssets.get())).append("\n");
+                sb.append(String.format(" Downloaded assets: %,32d files(%d zero-size)", _nbDownloadedAssets.get(),
+                        _nbDownloadedZeroSizeAssets.get())).append("\n");
             } else {
                 sb.append(String.format(" Downloaded assets: %,32d files", _nbDownloadedAssets.get())).append("\n");
             }
@@ -481,20 +474,12 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
                 case UPLOAD:
                     submitUploadJob(job);
                     break;
-                case SYNC:
-                    submitDownloadJob(job);
-                    submitUploadJob(job);
-                    break;
                 case CHECK_DOWNLOAD:
                     submitDownloadCheckJob(job);
                     break;
                 case CHECK_UPLOAD:
                     submitUploadCheckJob(job);
                     break;
-                case CHECK_SYNC:
-                    submitDownloadCheckJob(job);
-                    submitUploadCheckJob(job);
-                    break;
                 default:
                     break;
                 }
@@ -701,7 +686,7 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
     private void syncDeleteAssets() throws Throwable {
         List<Job> jobs = settings().jobs();
         for (Job job : jobs) {
-            if (job.action() == Action.UPLOAD || job.action() == Action.SYNC) {
+            if (job.action() == Action.UPLOAD) {
                 _queriers.submit(new SyncDeleteAssetsTask(session(), logger(), job, settings(), _nbDeletedAssets));
             }
         }
@@ -710,7 +695,7 @@ public abstract class MFSyncApp extends AbstractMFApp<unimelb.mf.client.sync.set
     private void syncDeleteFiles() throws Throwable {
         List<Job> jobs = settings().jobs();
         for (Job job : jobs) {
-            if (job.action() == Action.DOWNLOAD || job.action() == Action.SYNC) {
+            if (job.action() == Action.DOWNLOAD) {
                 _queriers.submit(
                         new SyncDeleteFilesTask(session(), logger(), job, settings(), _workers, _nbDeletedFiles));
             }
diff --git a/src/main/java/unimelb/mf/client/sync/cli/MFSync.java b/src/main/java/unimelb/mf/client/sync/cli/MFSync.java
deleted file mode 100644
index dc240ae392b5ee10a9ea62380d0398c06e9c9987..0000000000000000000000000000000000000000
--- a/src/main/java/unimelb/mf/client/sync/cli/MFSync.java
+++ /dev/null
@@ -1,330 +0,0 @@
-package unimelb.mf.client.sync.cli;
-
-import java.io.FileNotFoundException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import unimelb.mf.client.session.MFConfigurationBuilder;
-import unimelb.mf.client.session.MFSession;
-import unimelb.mf.client.sync.MFSyncApp;
-import unimelb.mf.client.sync.settings.Action;
-import unimelb.mf.client.sync.settings.Job;
-import unimelb.mf.client.util.AssetNamespaceUtils;
-import unimelb.mf.client.util.LoggingUtils;
-import unimelb.mf.client.util.PathUtils;
-
-public class MFSync extends MFSyncApp {
-
-    public static final String PROG = "unimelb-mf-sync";
-
-    private MFConfigurationBuilder _mfconfig;
-    private Map<Path, String> _dirNss;
-    private Map<String, Path> _nsDirs;
-
-    public MFSync() {
-        super();
-        settings().setCsumCheck(false);
-        settings().setVerbose(true);
-        settings().setDaemon(false);
-        _dirNss = new LinkedHashMap<Path, String>();
-        _nsDirs = new LinkedHashMap<String, Path>();
-    }
-
-    @Override
-    protected void preExecute() throws Throwable {
-        if (logger() == null) {
-            Logger logger = settings().logDirectory() == null ? LoggingUtils.createConsoleLogger()
-                    : LoggingUtils.createFileAndConsoleLogger(settings().logDirectory(), applicationName());
-            logger.setLevel(settings().verbose() ? Level.ALL : Level.WARNING);
-            setLogger(logger);
-        }
-    }
-
-    protected void printUsage() {
-        // @formatter:off
-        System.out.println();
-        System.out.println("USAGE:");
-        System.out.println(String.format("    %s [OPTIONS] <dir1> <namespace1> [<dir2> <namespace2>]", PROG));
-        System.out.println();
-        System.out.println("DESCRIPTION:");
-        System.out.println("    Synchronize local directory with remote asset namespace. The algorithm will ensure that the two end point sets of data are the same set, that is, the join of the two sets.  There are no deletions at either end");
-        System.out.println();
-        System.out.println("OPTIONS:");
-        System.out.println("    --mf.config <mflux.cfg>                   Path to the config file that contains Mediaflux server details and user credentials.");
-        System.out.println("    --mf.host <host>                          Mediaflux server host.");
-        System.out.println("    --mf.port <port>                          Mediaflux server port.");
-        System.out.println("    --mf.transport <https|http|tcp/ip>        Mediaflux server transport, can be http, https or tcp/ip.");
-        System.out.println("    --mf.auth <domain,user,password>          Mediaflux user credentials.");
-        System.out.println("    --mf.token <token>                        Mediaflux secure identity token.");
-
-        System.out.println("    --csum-check                              Files are equated if the name and size are the same. In addition, with this argument, you can optionally compute the CRC32 checksumk to decide if two files are the same.");
-        System.out.println("    --nb-queriers <n>                         Number of query threads. Defaults to " + unimelb.mf.client.sync.settings.Settings.DEFAULT_NUM_OF_QUERIERS);
-        System.out.println("    --nb-workers <n>                          Number of concurrent worker threads to download data. Defaults to " + unimelb.mf.client.sync.settings.Settings.DEFAULT_NUM_OF_WORKERS);
-        System.out.println("    --nb-retries <n>                          Retry times when error occurs. Defaults to " + unimelb.mf.client.sync.settings.Settings.DEFAULT_MAX_RETRIES);
-        System.out.println("    --batch-size <size>                       Size of the query result. Defaults to " + unimelb.mf.client.sync.settings.Settings.DEFAULT_BATCH_SIZE);
-        System.out.println("    --daemon                                  Run as a daemon.");
-        System.out.println("    --daemon-port <port>                      Daemon listener port if running as a daemon. Defaults to " + unimelb.mf.client.sync.settings.Settings.DEFAULT_DAEMON_LISTENER_PORT);
-        System.out.println("    --daemon-scan-interval <seconds>          Time interval (in seconds) between scans of source directories. Defaults to " + (unimelb.mf.client.sync.settings.Settings.DEFAULT_DAEMON_SCAN_INTERVAL/1000) +" seconds.");
-        System.out.println("    --log-dir <dir>                           Path to the directory for log files. No logging if not specified.");
-        System.out.println("    --notify <email-addresses>                When completes, send email notification to the recipients(comma-separated email addresses if multiple). Not applicable for daemon mode.");
-        System.out.println("    --quiet                                   Do not print progress messages.");
-        System.out.println("    --help                                    Prints usage.");
-        System.out.println();
-        System.out.println("POSITIONAL ARGUMENTS:");
-        System.out.println("    <dir>                                     Local directory path.");
-        System.out.println("    <namespace>                               Corresponding remote asset namespace path.");
-        System.out.println();
-        System.out.println(String.format("    %s --mf.config ~/.Arcitecta/mflux.cfg --nb-queriers 2 --nb-workers 4 ~/Documents/foo /projects/proj-1128.1.59/foo", PROG));
-        System.out.println();
-        // @formatter:on
-    }
-
-    protected void parseArgs(String[] args) throws Throwable {
-        if (_mfconfig == null) {
-            _mfconfig = new MFConfigurationBuilder().setConsoleLogon(true);
-            _mfconfig.findAndLoadFromConfigFile();
-            _mfconfig.setApp(applicationName());
-        }
-        if (args != null) {
-            try {
-                for (int i = 0; i < args.length;) {
-                    if ("--help".equalsIgnoreCase(args[i]) || "-h".equalsIgnoreCase(args[i])) {
-                        printUsage();
-                        System.exit(0);
-                    }
-                    int n = parseMFOptions(args, i);
-                    if (n > 0) {
-                        i += n;
-                        continue;
-                    }
-                    n = parseSyncOptions(args, i);
-                    if (n > 0) {
-                        i += n;
-                        continue;
-                    }
-                    Path dir = Paths.get(args[i]).toAbsolutePath();
-                    if (!Files.exists(dir)) {
-                        throw new IllegalArgumentException(new FileNotFoundException(args[i]));
-                    }
-                    if (!Files.isDirectory(dir)) {
-                        throw new IllegalArgumentException(args[i] + " is not a directory.");
-                    }
-                    String ns = args[i + 1];
-                    _nsDirs.put(ns, dir);
-                    _dirNss.put(dir, ns);
-                    i += 2;
-                }
-            } catch (ArrayIndexOutOfBoundsException e) {
-                throw new IllegalArgumentException(e);
-            }
-        }
-
-        if (_dirNss.isEmpty() || _nsDirs.isEmpty()) {
-            throw new IllegalArgumentException("Missing local directory and remote asset namespace arguments.");
-        }
-
-        /*
-         * test MF authentication
-         */
-        MFSession session = MFSession.create(_mfconfig);
-
-        /*
-         * set MF session
-         */
-        setSession(session);
-        /*
-         * add jobs
-         */
-        Set<String> nss = _nsDirs.keySet();
-        for (String ns : nss) {
-            String pns = PathUtils.getParentPath(ns);
-            if (!AssetNamespaceUtils.assetNamespaceExists(session, pns)) {
-                throw new IllegalArgumentException("Asset namespace: '" + pns + "' does not exist.");
-            }
-            Path dir = _nsDirs.get(ns);
-            String dirName = dir.getFileName().toString();
-            String nsName = PathUtils.getFileName(ns);
-            if (!dirName.equals(nsName)) {
-                if (!AssetNamespaceUtils.assetNamespaceExists(session, ns)) {
-                    throw new IllegalArgumentException("Asset namespace: '" + ns + "' does not exist.");
-                }
-            }
-            settings().addJob(new Job(Action.SYNC, dir, ns, false));
-        }
-    }
-
-    protected int parseMFOptions(String[] args, int i) throws Throwable {
-        if ("--mf.config".equalsIgnoreCase(args[i])) {
-            try {
-                _mfconfig.loadFromConfigFile(args[i + 1]);
-            } catch (Throwable e) {
-                throw new IllegalArgumentException("Invalid --mf.config: " + args[i + 1], e);
-            }
-            return 2;
-        } else if ("--mf.host".equalsIgnoreCase(args[i])) {
-            _mfconfig.setHost(args[i + 1]);
-            return 2;
-        } else if ("--mf.port".equalsIgnoreCase(args[i])) {
-            _mfconfig.setPort(Integer.parseInt(args[i + 1]));
-            return 2;
-        } else if ("--mf.transport".equalsIgnoreCase(args[i])) {
-            _mfconfig.setTransport(args[i + 1]);
-            return 2;
-        } else if ("--mf.auth".equalsIgnoreCase(args[i])) {
-            String auth = args[i + 1];
-            String[] parts = auth.split(",");
-            if (parts == null || parts.length != 3) {
-                throw new IllegalArgumentException("Invalid mf.auth: " + auth);
-            }
-            _mfconfig.setUserCredentials(parts[0], parts[1], parts[2]);
-            return 2;
-        } else if ("--mf.token".equalsIgnoreCase(args[i])) {
-            _mfconfig.setToken(args[i + 1]);
-            return 2;
-        } else {
-            return 0;
-        }
-    }
-
-    protected int parseSyncOptions(String[] args, int i) throws Throwable {
-        if ("--csum-check".equalsIgnoreCase(args[i])) {
-            settings().setCsumCheck(true);
-            return 1;
-        } else if ("--batch-size".equalsIgnoreCase(args[i])) {
-            try {
-                int batchSize = Integer.parseInt(args[i + 1]);
-                if (batchSize <= 0) {
-                    throw new IllegalArgumentException("Invalid --batch-size " + args[i + 1]
-                            + " Expects a positive integer, found " + args[i + 1]);
-                }
-                settings().setBatchSize(batchSize);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException(
-                        "Invalid --batch-size " + args[i + 1] + " Expects a positive integer, found " + args[i + 1],
-                        nfe);
-            }
-        } else if ("--nb-queriers".equalsIgnoreCase(args[i])) {
-            try {
-                int nbQueriers = Integer.parseInt(args[i + 1]);
-                if (nbQueriers <= 0) {
-                    throw new IllegalArgumentException("Invalid --nb-queriers " + args[i + 1]
-                            + " Expects a positive integer, found " + args[i + 1]);
-                }
-                settings().setNumberOfQueriers(nbQueriers);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException(
-                        "Invalid --nb-queriers " + args[i + 1] + " Expects a positive integer, found " + args[i + 1],
-                        nfe);
-            }
-        } else if ("--nb-workers".equalsIgnoreCase(args[i])) {
-            try {
-                int nbWorkers = Integer.parseInt(args[i + 1]);
-                if (nbWorkers <= 0) {
-                    throw new IllegalArgumentException("Invalid --nb-workers " + args[i + 1]
-                            + " Expects a positive integer, found " + args[i + 1]);
-                }
-                settings().setNumberOfWorkers(nbWorkers);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException(
-                        "Invalid --nb-workers " + args[i + 1] + " Expects a positive integer, found " + args[i + 1],
-                        nfe);
-            }
-        } else if ("--nb-retries".equalsIgnoreCase(args[i])) {
-            try {
-                int nbRetries = Integer.parseInt(args[i + 1]);
-                settings().setMaxRetries(nbRetries);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException(
-                        "Invalid --nb-retries " + args[i + 1] + " Expects a positive integer, found " + args[i + 1],
-                        nfe);
-            }
-        } else if ("--daemon".equalsIgnoreCase(args[i])) {
-            settings().setDaemon(true);
-            return 1;
-        } else if ("--daemon-port".equalsIgnoreCase(args[i])) {
-            try {
-                int port = Integer.parseInt(args[i + 1]);
-                if (port < 0 || port > 65535) {
-                    throw new IllegalArgumentException("Invalid --daemon-port " + args[i + 1]
-                            + " Expects a positive integer, found " + args[i + 1]);
-                }
-                settings().setDaemonListenerPort(port);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException("Invalid --daemon-port " + args[i + 1]
-                        + " Expects a positive integer in the range of [0, 65535], found " + args[i + 1], nfe);
-            }
-        } else if ("--daemon-scan-interval".equalsIgnoreCase(args[i])) {
-            try {
-                int intervalSeconds = Integer.parseInt(args[i + 1]);
-                if (intervalSeconds <= 0) {
-                    throw new IllegalArgumentException("Invalid --daemon-scan-interval " + args[i + 1]
-                            + " Expects a positive integer, found " + args[i + 1]);
-                }
-                settings().setDaemonScanInterval(((long) intervalSeconds) * 1000L);
-                return 2;
-            } catch (NumberFormatException nfe) {
-                throw new IllegalArgumentException("Invalid --daemon-scan-interval " + args[i + 1]
-                        + " Expects a positive integer, found " + args[i + 1], nfe);
-            }
-        } else if ("--log-dir".equalsIgnoreCase(args[i])) {
-            Path logDir = Paths.get(args[i + 1]).toAbsolutePath();
-            if (!Files.exists(logDir)) {
-                throw new IllegalArgumentException("Directory: " + args[i] + " does not exist.");
-            }
-            if (!Files.isDirectory(logDir)) {
-                throw new IllegalArgumentException(args[i] + " is not a directory.");
-            }
-            settings().setLogDirectory(logDir);
-            return 2;
-        } else if ("--notify".equalsIgnoreCase(args[i])) {
-            String[] emails = args[i + 1].indexOf(',') != -1 ? args[i + 1].split(",") : new String[] { args[i + 1] };
-            settings().addRecipients(emails);
-            return 2;
-        } else if ("--quiet".equalsIgnoreCase(args[i])) {
-            settings().setVerbose(false);
-            return 1;
-        } else {
-            return 0;
-        }
-    }
-
-    @Override
-    public final String applicationName() {
-        return PROG;
-    }
-
-    public static void main(String[] args) throws Throwable {
-        MFSync app = new MFSync();
-        try {
-            app.parseArgs(args);
-        } catch (IllegalArgumentException iae) {
-            System.err.println(iae.getMessage());
-            app.printUsage();
-            System.exit(1);
-        }
-        if (app.settings().daemon()) {
-            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
-
-                @Override
-                public void run() {
-                    app.interrupt();
-                }
-            }));
-            new Thread(app).start();
-        } else {
-            app.execute();
-        }
-    }
-
-}
diff --git a/src/main/java/unimelb/mf/client/sync/cli/MFUpload.java b/src/main/java/unimelb/mf/client/sync/cli/MFUpload.java
index f17ddfb1b95620e125b44bf295c1f446bf5396fa..fd4cb89818f711863ed90f52b81e3e738db905a4 100644
--- a/src/main/java/unimelb/mf/client/sync/cli/MFUpload.java
+++ b/src/main/java/unimelb/mf/client/sync/cli/MFUpload.java
@@ -1,5 +1,6 @@
 package unimelb.mf.client.sync.cli;
 
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -21,16 +22,11 @@ public class MFUpload extends MFSyncApp {
 
     public static final String PROG = "unimelb-mf-upload";
 
-    private MFConfigurationBuilder _mfconfig;
-    private Set<Path> _dirs;
-    private String _rootNS;
-
     public MFUpload() {
         super();
         settings().setCsumCheck(false);
         settings().setVerbose(true);
         settings().setDaemon(false);
-        _dirs = new LinkedHashSet<Path>();
     }
 
     @Override
@@ -48,11 +44,13 @@ public class MFUpload extends MFSyncApp {
         System.out.println();
         System.out.println("USAGE:");
         System.out.println(String.format("    %s [OPTIONS] --namespace <dst-namespace> <src-dir1> [<src-dir2>...]", PROG));
+        System.out.println(String.format("    %s --config <config.xml>", PROG));
         System.out.println();
         System.out.println("DESCRIPTION:");
         System.out.println("    Upload local files to Mediaflux.  If the file pre-exists in Mediaflux and is the same as that being uploaded, the Mediaflux asset is not modified.  However, if the files are different, then a new version of the asset will be created.   In Daemon mode, the process will only upload new files since the process last executed.");
         System.out.println();
         System.out.println("OPTIONS:");
+        System.out.println("    --config <config.xml>                     A single configuration file including all required settings (Mediaflux server details, user credentials, application settings). If conflicts with all the other options.");
         System.out.println("    --mf.config <mflux.cfg>                   Path to the config file that contains Mediaflux server details and user credentials.");
         System.out.println("    --mf.host <host>                          Mediaflux server host.");
         System.out.println("    --mf.port <port>                          Mediaflux server port.");
@@ -86,23 +84,65 @@ public class MFUpload extends MFSyncApp {
     }
 
     protected void parseArgs(String[] args) throws Throwable {
-        if (_mfconfig == null) {
-            _mfconfig = new MFConfigurationBuilder().setConsoleLogon(true);
-            _mfconfig.findAndLoadFromConfigFile();
-            _mfconfig.setApp(applicationName());
+
+        // must have args
+        if (args == null || args.length == 0) {
+            throw new IllegalArgumentException("Missing arguments.");
         }
-        if (args != null) {
+
+        if (args.length == 2 && "--config".equalsIgnoreCase(args[0])) {
+
+            // settings from single configuration file (.xml)
+
+            Path configPath = (args[1].indexOf('/') < 0 && args[1].indexOf(File.separatorChar) < 0)
+                    ? Paths.get(System.getProperty("user.dir"), args[1])
+                    : Paths.get(args[1]);
+            if (!Files.isRegularFile(configPath)) {
+                throw new IllegalArgumentException(
+                        "--config file: " + args[1] + " does not exist or is not a regular file.");
+            }
+            MFConfigurationBuilder mfconfig = new MFConfigurationBuilder().setConsoleLogon(true);
+            mfconfig.loadFromXmlFile(configPath.toFile());
+            mfconfig.setApp(applicationName());
+
+            MFSession session = MFSession.create(mfconfig);
+
+            setSession(session);
+
+            settings().loadFromXmlFile(configPath, session);
+
+        } else {
+
+            // settings from all sort of arguments...
+
+            MFConfigurationBuilder mfconfig = new MFConfigurationBuilder().setConsoleLogon(true);
+            mfconfig.findAndLoadFromConfigFile();
+            mfconfig.setApp(applicationName());
+
+            Set<Path> dirs = new LinkedHashSet<Path>();
+            String rootNS = null;
+
             try {
                 for (int i = 0; i < args.length;) {
                     if ("--help".equalsIgnoreCase(args[i]) || "-h".equalsIgnoreCase(args[i])) {
                         printUsage();
                         System.exit(0);
+                    } else if ("--namespace".equalsIgnoreCase(args[i])) {
+                        if (rootNS == null) {
+                            rootNS = args[i + 1];
+                        } else {
+                            throw new IllegalArgumentException("Expects only on --namespace argument. Found multiple.");
+                        }
+                        i += 2;
+                        continue;
                     }
-                    int n = parseMFOptions(args, i);
+
+                    int n = parseMFOptions(args, i, mfconfig);
                     if (n > 0) {
                         i += n;
                         continue;
                     }
+
                     n = parseUploadOptions(args, i);
                     if (n > 0) {
                         i += n;
@@ -115,58 +155,58 @@ public class MFUpload extends MFSyncApp {
                     if (!Files.isDirectory(dir)) {
                         throw new IllegalArgumentException(args[i] + " is not a directory.");
                     }
-                    _dirs.add(dir);
+                    dirs.add(dir);
                     i++;
                 }
             } catch (ArrayIndexOutOfBoundsException e) {
                 throw new IllegalArgumentException(e);
             }
-        }
 
-        if (_rootNS == null) {
-            throw new IllegalArgumentException("Missing destination asset namespace: --namespace argument.");
-        }
-        if (_dirs.isEmpty()) {
-            throw new IllegalArgumentException("Missing source directory.");
-        }
+            if (rootNS == null) {
+                throw new IllegalArgumentException("Missing destination asset namespace: --namespace argument.");
+            }
+            if (dirs.isEmpty()) {
+                throw new IllegalArgumentException("Missing source directory.");
+            }
 
-        /*
-         * test MF authentication
-         */
-        MFSession session = MFSession.create(_mfconfig);
+            /*
+             * authenticate to get session
+             */
+            MFSession session = MFSession.create(mfconfig);
 
-        /*
-         * set MF session
-         */
-        setSession(session);
+            /*
+             * associate with session
+             */
+            setSession(session);
 
-        if (!AssetNamespaceUtils.assetNamespaceExists(session, _rootNS)) {
-            throw new IllegalArgumentException("Asset namespace: '" + _rootNS + "' does not exist.");
-        }
-        /*
-         * add jobs
-         */
-        for (Path dir : _dirs) {
-            settings().addJob(new Job(Action.UPLOAD, dir, _rootNS, true));
+            if (!AssetNamespaceUtils.assetNamespaceExists(session, rootNS)) {
+                throw new IllegalArgumentException("Asset namespace: '" + rootNS + "' does not exist.");
+            }
+            /*
+             * add jobs
+             */
+            for (Path dir : dirs) {
+                settings().addJob(new Job(Action.UPLOAD, dir, rootNS, true));
+            }
         }
     }
 
-    protected int parseMFOptions(String[] args, int i) throws Throwable {
+    protected int parseMFOptions(String[] args, int i, MFConfigurationBuilder mfconfig) throws Throwable {
         if ("--mf.config".equalsIgnoreCase(args[i])) {
             try {
-                _mfconfig.loadFromConfigFile(args[i + 1]);
+                mfconfig.loadFromConfigFile(args[i + 1]);
             } catch (Throwable e) {
                 throw new IllegalArgumentException("Invalid --mf.config: " + args[i + 1], e);
             }
             return 2;
         } else if ("--mf.host".equalsIgnoreCase(args[i])) {
-            _mfconfig.setHost(args[i + 1]);
+            mfconfig.setHost(args[i + 1]);
             return 2;
         } else if ("--mf.port".equalsIgnoreCase(args[i])) {
-            _mfconfig.setPort(Integer.parseInt(args[i + 1]));
+            mfconfig.setPort(Integer.parseInt(args[i + 1]));
             return 2;
         } else if ("--mf.transport".equalsIgnoreCase(args[i])) {
-            _mfconfig.setTransport(args[i + 1]);
+            mfconfig.setTransport(args[i + 1]);
             return 2;
         } else if ("--mf.auth".equalsIgnoreCase(args[i])) {
             String auth = args[i + 1];
@@ -174,10 +214,10 @@ public class MFUpload extends MFSyncApp {
             if (parts == null || parts.length != 3) {
                 throw new IllegalArgumentException("Invalid mf.auth: " + auth);
             }
-            _mfconfig.setUserCredentials(parts[0], parts[1], parts[2]);
+            mfconfig.setUserCredentials(parts[0], parts[1], parts[2]);
             return 2;
         } else if ("--mf.token".equalsIgnoreCase(args[i])) {
-            _mfconfig.setToken(args[i + 1]);
+            mfconfig.setToken(args[i + 1]);
             return 2;
         } else {
             return 0;
@@ -185,14 +225,8 @@ public class MFUpload extends MFSyncApp {
     }
 
     protected int parseUploadOptions(String[] args, int i) throws Throwable {
-        if ("--namespace".equalsIgnoreCase(args[i])) {
-            if (_rootNS == null) {
-                _rootNS = args[i + 1];
-            } else {
-                throw new IllegalArgumentException("Expects only on --namespace argument. Found multiple.");
-            }
-            return 2;
-        } else if ("--csum-check".equalsIgnoreCase(args[i])) {
+
+        if ("--csum-check".equalsIgnoreCase(args[i])) {
             settings().setCsumCheck(true);
             return 1;
         } else if ("--batch-size".equalsIgnoreCase(args[i])) {
diff --git a/src/main/java/unimelb/mf/client/sync/settings/Action.java b/src/main/java/unimelb/mf/client/sync/settings/Action.java
index 6f772e28b49d686711dabb42031dcbd3bca06714..04ca7d60eb48ffbe34c8fbc9a1caff0491e29c1f 100644
--- a/src/main/java/unimelb/mf/client/sync/settings/Action.java
+++ b/src/main/java/unimelb/mf/client/sync/settings/Action.java
@@ -5,10 +5,8 @@ public enum Action {
     // @formatter:off
     UPLOAD(Type.TRANSFER, Direction.UP), 
     DOWNLOAD(Type.TRANSFER, Direction.DOWN), 
-    SYNC(Type.TRANSFER, Direction.BOTH),
     CHECK_UPLOAD(Type.CHECK, Direction.UP),
-    CHECK_DOWNLOAD(Type.CHECK, Direction.DOWN),
-    CHECK_SYNC(Type.CHECK, Direction.BOTH);
+    CHECK_DOWNLOAD(Type.CHECK, Direction.DOWN);
     // @formatter:on
 
     private Type _type;
@@ -49,7 +47,7 @@ public enum Action {
     }
 
     public static enum Direction {
-        UP, DOWN, BOTH;
+        UP, DOWN;
 
         public static Direction fromString(String d) {
             if (d != null) {
@@ -69,19 +67,15 @@ public enum Action {
             switch (direction) {
             case UP:
                 return Action.UPLOAD;
-            case DOWN:
-                return Action.DOWNLOAD;
             default:
-                return Action.SYNC;
+                return Action.DOWNLOAD;
             }
         } else {
             switch (direction) {
             case UP:
                 return Action.CHECK_UPLOAD;
-            case DOWN:
-                return Action.CHECK_DOWNLOAD;
             default:
-                return Action.CHECK_SYNC;
+                return Action.CHECK_DOWNLOAD;
             }
         }
     }
diff --git a/src/main/java/unimelb/mf/client/sync/settings/Job.java b/src/main/java/unimelb/mf/client/sync/settings/Job.java
index dc2fb3e525d2aba417c12be1b948a3f54744ff4b..52c6ab2ef1dd36e40257efbe529c8c35b15347ce 100644
--- a/src/main/java/unimelb/mf/client/sync/settings/Job.java
+++ b/src/main/java/unimelb/mf/client/sync/settings/Job.java
@@ -1,12 +1,14 @@
 package unimelb.mf.client.sync.settings;
 
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
+import arc.xml.XmlDoc;
 import unimelb.mf.client.session.MFSession;
 import unimelb.mf.client.sync.settings.Action.Direction;
 import unimelb.mf.client.util.AssetNamespaceUtils;
@@ -19,37 +21,88 @@ public class Job {
     private String _ns;
     private String _store;
     private Path _dir;
-    private List<String> _includes;
-    private List<String> _excludes;
+    private Set<String> _includes;
+    private Set<String> _excludes;
 
+    /**
+     * 
+     * @param action
+     * @param dir
+     *            source directory.
+     * @param ns
+     *            destination asset namespace
+     */
     public Job(Action action, Path dir, String ns) {
         this(action, dir, ns, false, null, null);
     }
 
+    /**
+     * 
+     * @param action
+     * 
+     * @param dir
+     *            source directory
+     * @param ns
+     *            destination asset namespace
+     * @param isParentNS
+     *            Is destination namespace a parent namespace?
+     */
     public Job(Action action, Path dir, String ns, boolean isParentNS) {
         this(action, dir, ns, isParentNS, null, null);
     }
 
+    /**
+     * 
+     * @param action
+     * @param dir
+     *            source directory
+     * @param ns
+     *            destination asset namespace
+     * @param isParentNS
+     *            Is namespace a parent namespace?
+     * @param includes
+     *            Patterns to include source files.
+     * @param excludes
+     *            Patterns to exclude source files.
+     */
     public Job(Action action, Path dir, String ns, boolean isParentNS, Collection<String> includes,
             Collection<String> excludes) {
         _action = action;
         _dir = dir;
         _ns = isParentNS ? PathUtils.joinSystemIndependent(ns, dir.getFileName().toString()) : ns;
         if (includes != null && !includes.isEmpty()) {
-            _includes = new ArrayList<String>();
+            _includes = new LinkedHashSet<String>();
             _includes.addAll(includes);
         }
 
         if (excludes != null && !excludes.isEmpty()) {
-            _excludes = new ArrayList<String>();
+            _excludes = new LinkedHashSet<String>();
             _excludes.addAll(excludes);
         }
     }
 
+    /**
+     * 
+     * @param action
+     * @param ns
+     *            source asset namespace
+     * @param dir
+     *            destination directory
+     */
     public Job(Action action, String ns, Path dir) {
         this(action, ns, dir, false, null, null);
     }
 
+    /**
+     * 
+     * @param action
+     * @param ns
+     *            source asset namespace
+     * @param dir
+     *            destination directory
+     * @param isParentDir
+     *            Is the directory parent directory?
+     */
     public Job(Action action, String ns, Path dir, boolean isParentDir) {
         this(action, ns, dir, isParentDir, null, null);
     }
@@ -60,16 +113,62 @@ public class Job {
         _dir = isParentDir ? Paths.get(dir.toString(), PathUtils.getLastComponent(ns)) : dir;
         _ns = ns;
         if (includes != null && !includes.isEmpty()) {
-            _includes = new ArrayList<String>();
+            _includes = new LinkedHashSet<String>();
             _includes.addAll(includes);
         }
 
         if (excludes != null && !excludes.isEmpty()) {
-            _excludes = new ArrayList<String>();
+            _excludes = new LinkedHashSet<String>();
             _excludes.addAll(excludes);
         }
     }
 
+    public Job(XmlDoc.Element je, Path xmlFile, MFSession session) throws Throwable {
+
+        // TODO support project id
+        _action = Action.fromString(je.value("@action"));
+        if (_action == null) {
+            throw new IllegalArgumentException(
+                    "Invalid job configuration found in file: " + xmlFile + ". Missing or invalid @action attribute.");
+        }
+
+        String ns = je.value("namespace");
+        if (ns == null) {
+            throw new IllegalArgumentException(
+                    "Invalid job configuration found in file: " + xmlFile + ". Missing namespace element.");
+        }
+        if (!AssetNamespaceUtils.assetNamespaceExists(session, ns)) {
+            throw new IllegalArgumentException("Invalid job configuration found in file: '" + xmlFile
+                    + "': Asset namespace: '" + ns + "' does not exist.");
+        }
+        boolean isParentNS = _action.direction() != Direction.DOWN ? je.booleanValue("namespace/@parent", false)
+                : false;
+
+        String dir = je.value("directory");
+        if (dir == null) {
+            throw new IllegalArgumentException(
+                    "Invalid job found in the XML configuration file: " + xmlFile + ". Missing directory element.");
+        }
+        Path directory = Paths.get(dir).toAbsolutePath();
+        if (!Files.isDirectory(directory)) {
+            throw new IllegalArgumentException("Invalid job configuration found in file: '" + xmlFile
+                    + "': Directory: '" + directory + "' does not exist or it is not a directory.");
+        }
+        boolean isParentDir = _action.direction() != Direction.UP ? je.booleanValue("directory/@parent", false) : false;
+
+        _ns = isParentNS ? PathUtils.joinSystemIndependent(ns, directory.getFileName().toString()) : ns;
+        _dir = isParentDir ? Paths.get(dir.toString(), PathUtils.getLastComponent(ns)) : directory;
+
+        if (je.elementExists("include")) {
+            _includes = new LinkedHashSet<String>();
+            _includes.addAll(je.values("include"));
+        }
+        if (je.elementExists("exclude")) {
+            _excludes = new LinkedHashSet<String>();
+            _excludes.addAll(je.values("exclude"));
+        }
+    }
+
     public final Action action() {
         return _action;
     }
@@ -99,16 +198,16 @@ public class Job {
         return _dir;
     }
 
-    public final List<String> sourcePathExcludes() {
+    public final Set<String> sourcePathExcludes() {
         if (_excludes != null) {
-            Collections.unmodifiableList(_excludes);
+            Collections.unmodifiableSet(_excludes);
         }
         return null;
     }
 
-    public final List<String> sourcePathIncludes() {
+    public final Set<String> sourcePathIncludes() {
         if (_includes != null) {
-            Collections.unmodifiableList(_includes);
+            Collections.unmodifiableSet(_includes);
         }
         return null;
     }
diff --git a/src/main/java/unimelb/mf/client/sync/settings/Settings.java b/src/main/java/unimelb/mf/client/sync/settings/Settings.java
index 70261357b39854de0ce6c3559a7601b8dd48190c..a1f1ed78c20893b307c42c002036da46f26622ae 100644
--- a/src/main/java/unimelb/mf/client/sync/settings/Settings.java
+++ b/src/main/java/unimelb/mf/client/sync/settings/Settings.java
@@ -17,11 +17,11 @@ import java.util.Set;
 import arc.xml.XmlDoc;
 import unimelb.mf.client.session.MFSession;
 import unimelb.mf.client.sync.check.CheckHandler;
-import unimelb.mf.client.sync.settings.Action.Direction;
 import unimelb.mf.client.sync.task.AssetDownloadTask;
 import unimelb.mf.client.sync.task.AssetDownloadTask.Unarchive;
 import unimelb.mf.client.task.MFApp;
 import unimelb.mf.client.util.AssetNamespaceUtils;
+import unimelb.mf.client.util.PathUtils;
 
 public class Settings implements MFApp.Settings {
 
@@ -176,21 +176,34 @@ public class Settings implements MFApp.Settings {
 
     public void validate(MFSession session) throws Throwable {
         for (Job job : _jobs) {
+            /*
+             * check if namespace is specified
+             */
             if (job.namespace() == null) {
                 throw new IllegalArgumentException("Asset namespace is null.", new NullPointerException());
             }
-            if (!AssetNamespaceUtils.assetNamespaceExists(session, job.namespace())) {
-                throw new IllegalArgumentException("Asset namespace: '" + job.namespace() + "' does not exist.");
+            /*
+             * check if parent namespace exists
+             */
+            String parentNS = job.parentNamespace();
+            boolean parentNSExists = !AssetNamespaceUtils.assetNamespaceExists(session,
+                    PathUtils.getParentPath(parentNS));
+            if (!parentNSExists) {
+                throw new IllegalArgumentException("Asset namespace: '" + parentNS + "' does not exist.");
             }
+
+            /*
+             * check if directory is specified
+             */
             if (job.directory() == null) {
                 throw new IllegalArgumentException("Source directory is null.", new NullPointerException());
             }
-            if (!Files.exists(job.directory())) {
-                throw new IllegalArgumentException("Source directory: '" + job.directory() + "' does not exist",
-                        new FileNotFoundException(job.directory().toString()));
-            }
-            if (!Files.isDirectory(job.directory())) {
-                throw new IllegalArgumentException("'" + job.directory() + "' is not a directory.");
+            /*
+             * check if parent directory exists
+             */
+            Path parentDir = job.directory().toAbsolutePath().getParent();
+            if (!Files.isDirectory(parentDir)) {
+                throw new IllegalArgumentException("'" + parentDir + "' does not exist or it is not a directory.");
             }
         }
     }
@@ -326,21 +339,26 @@ public class Settings implements MFApp.Settings {
         return true;
     }
 
-    public void loadFromXmlFile(Path xmlFile) throws Throwable {
+    public void loadFromXmlFile(Path xmlFile, MFSession session) throws Throwable {
         if (xmlFile != null) {
             Reader r = new BufferedReader(new FileReader(xmlFile.toFile()));
             try {
-                XmlDoc.Element pe = new XmlDoc().parse(r).element("properties");
+                XmlDoc.Element pe = new XmlDoc().parse(r);
+                if (pe == null) {
+                    throw new IllegalArgumentException("Failed to parse configuration XML file: " + xmlFile);
+                }
+
                 XmlDoc.Element se = pe.element("sync/settings");
                 if (se == null) {
-                    throw new Exception("element properties/sync/settings is not found.");
+                    throw new Exception("Failed to parse configuration XML file: '" + xmlFile
+                            + "'. Element properties/sync/settings is not found.");
                 }
 
                 if (se.elementExists("numberOfQueriers")) {
                     setNumberOfQueriers(se.intValue("numberOfQueriers"));
                 }
                 if (se.elementExists("numberOfWorkers")) {
-                    setNumberOfQueriers(se.intValue("numberOfWorkers"));
+                    setNumberOfWorkers(se.intValue("numberOfWorkers"));
                 }
                 if (se.elementExists("batchSize")) {
                     setBatchSize(se.intValue("batchSize"));
@@ -395,56 +413,7 @@ public class Settings implements MFApp.Settings {
                 List<XmlDoc.Element> jes = pe.elements("sync/job");
                 if (jes != null) {
                     for (XmlDoc.Element je : jes) {
-                        // TODO support project id and include/exclude
-                        // patterns...
-
-                        // @formatter:off
-                        Action action = Action.fromString(je.value("@action"));
-                        if (action == null) {
-                            System.err.println("Warning: Invalid job found in the XML configuration file. Missing or invalid @action attribute.");
-                            continue;
-//                            throw new IllegalArgumentException(
-//                                    "Invalid job found in the XML configuration file. Missing or invalid @action attribute.");
-                        }
-                        String ns = je.value("namespace");
-                        if (ns == null) {
-                            System.err.println("Warning: Invalid job found in the XML configuration file. Missing namespace element.");
-                            continue;
-//                            throw new IllegalArgumentException(
-//                                    "Invalid job found in the XML configuration file. Missing namespace element.");
-                        }
-                        
-                        
-                        String dir = je.value("directory");
-                        if (dir == null) {
-                            System.err.println("Warning: Invalid job found in the XML configuration file. Missing directory element.");
-                            continue;
-//                            throw new IllegalArgumentException(
-//                                    "Invalid job found in the XML configuration file. Missing directory element.");
-                        }
-                        //@formatter:on
-
-                        boolean isNSParent = action.direction() != Direction.DOWN
-                                ? je.booleanValue("namespace/@parent", false)
-                                : false;
-                        boolean isDirParent = action.direction() != Direction.UP
-                                ? je.booleanValue("directory/@parent", false)
-                                : false;
-
-                        switch (action.direction()) {
-                        case UP:
-                            addJob(new Job(action, Paths.get(dir).toAbsolutePath(), ns, isNSParent));
-                            break;
-                        case DOWN:
-                            addJob(new Job(action, ns, Paths.get(dir).toAbsolutePath(), isDirParent));
-                            break;
-                        case BOTH:
-                            addJob(new Job(action, Paths.get(dir).toAbsolutePath(), ns, isNSParent));
-                            addJob(new Job(action, ns, Paths.get(dir).toAbsolutePath(), isDirParent));
-                            break;
-                        default:
-                            break;
-                        }
+                        addJob(new Job(je, xmlFile, session));
                     }
                 }
             } finally {
@@ -453,17 +422,6 @@ public class Settings implements MFApp.Settings {
         }
     }
 
-    public boolean hasSyncJobs() {
-        if (_jobs != null && !_jobs.isEmpty()) {
-            for (Job job : _jobs) {
-                if (job.action() == Action.SYNC) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public boolean hasDownloadJobs() {
         if (_jobs != null && !_jobs.isEmpty()) {
             for (Job job : _jobs) {
@@ -521,11 +479,11 @@ public class Settings implements MFApp.Settings {
     }
 
     public boolean needToDeleteFiles() {
-        return (hasDownloadJobs() || hasSyncJobs()) && deleteFiles();
+        return (hasDownloadJobs()) && deleteFiles();
     }
 
     public boolean needToDeleteAssets() {
-        return (hasUploadJobs() || hasSyncJobs()) && deleteAssets();
+        return (hasUploadJobs()) && deleteAssets();
     }
 
 }
diff --git a/src/main/java/unimelb/mf/client/sync/task/FileUploadTask.java b/src/main/java/unimelb/mf/client/sync/task/FileUploadTask.java
index 04a6b1598cde2537740239a22c594af348a30a42..86003cfce9cf5d4f92789166e42a22858e72dc8d 100644
--- a/src/main/java/unimelb/mf/client/sync/task/FileUploadTask.java
+++ b/src/main/java/unimelb/mf/client/sync/task/FileUploadTask.java
@@ -162,7 +162,8 @@ public class FileUploadTask extends AbstractMFTask {
             if (_ul != null) {
                 _ul.transferCompleted(_file, _assetPath);
             }
-            logInfo("Uploaded file: '" + _file + "' to asset(id=" + _assetId + "): '" + _assetPath + "'");
+            logInfo("Uploaded file: '" + _file + "' to asset" + (_assetId != null ? "(id=" + _assetId + ")" : "")
+                    + ": '" + _assetPath + "'");
         } catch (Throwable e) {
             if (_ul != null) {
                 _ul.transferFailed(_file, _assetPath);
diff --git a/src/main/scripts/unix/unimelb-mf-sync b/src/main/scripts/unix/unimelb-mf-sync
deleted file mode 100644
index 4913a1478ab87450f034c8d726b3227f44c0de24..0000000000000000000000000000000000000000
--- a/src/main/scripts/unix/unimelb-mf-sync
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-
-# ${ROOT}/bin/
-BIN=$(dirname ${BASH_SOURCE[0]})
-
-# current directory
-CWD=$(pwd)
-
-# ${ROOT}/
-ROOT=$(cd ${BIN}/../../ && pwd && cd ${CWD})
-
-# ${ROOT}/lib/
-LIB=${ROOT}/lib
-
-# ${ROOT}/lib/unimelb-mf-clients.jar
-JAR=${LIB}/unimelb-mf-clients.jar
-
-# check if unimelb-mf-clients.jar exists
-[[ ! -f $JAR ]] && echo "${JAR} is not found." >&2 && exit 2
-
-#export JAVA_HOME=${ROOT}/@JAVA_HOME@
-#export PATH=${JAVA_HOME}/bin:${PATH}
-
-# check if java exists
-[[ -z $(which java) ]] && echo "Java is not found." >&2 && exit 1
-
-# execute the command
-java -Xmx200m -cp "${JAR}" unimelb.mf.client.sync.cli.MFSync ${1+"$@"}
diff --git a/src/main/scripts/windows/unimelb-mf-sync.cmd b/src/main/scripts/windows/unimelb-mf-sync.cmd
deleted file mode 100644
index f565305e86fdcf615418cbcce8cae3571236850d..0000000000000000000000000000000000000000
--- a/src/main/scripts/windows/unimelb-mf-sync.cmd
+++ /dev/null
@@ -1,12 +0,0 @@
-@echo off
-
-pushd %~dp0..\..\
-set ROOT=%cd%
-popd
-
-@REM set JAVA_HOME=%ROOT%\@JAVA_HOME@
-@REM set PATH=%JAVA_HOME%\bin;%PATH%
-
-set JAR=%ROOT%\lib\unimelb-mf-clients.jar
-
-java -Xmx200m -cp "%JAR%" unimelb.mf.client.sync.cli.MFSync %*