Bug 516979 - File Manager API should be useable without a ProjectSpace

* FileTransferManager offers second contructor which does not require a
project space but project id and usersession, as these are the required
objects for the server call and are accesible with actually checking out
a project.
* Introduce abstraction of an upload queue as the projectspace was used
for this directely and offer an additional in memory option which is
used when no projectspace is available.
* new API remains internal for now
* Make sure required parent directories are created if the file id
contains path separators
* Add test cases

Change-Id: I432bfcd1b65dc9c7c33e0ae0173bfeefb0bbb2de
Signed-off-by: Johannes Faltermeier <jfaltermeier@eclipsesource.com>
diff --git a/bundles/org.eclipse.emf.emfstore.client/META-INF/MANIFEST.MF b/bundles/org.eclipse.emf.emfstore.client/META-INF/MANIFEST.MF
index 95d0cd0..5503e79 100644
--- a/bundles/org.eclipse.emf.emfstore.client/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.emf.emfstore.client/META-INF/MANIFEST.MF
@@ -79,6 +79,7 @@
    org.eclipse.emf.emfstore.test.common,
    org.eclipse.emf.emfstore.client.ui.test",
  org.eclipse.emf.emfstore.internal.client.model.filetransfer;version="1.9.0";x-friends:="org.eclipse.emf.emfstore.client.test",
+ org.eclipse.emf.emfstore.internal.client.model.filetransfer.util;version="1.9.0";x-internal:=true,
  org.eclipse.emf.emfstore.internal.client.model.impl;version="1.9.0";
   x-friends:="org.eclipse.emf.emfstore.client.test,
    org.eclipse.emf.emfstore.client.ui,
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileDownloadStatus.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileDownloadStatus.java
index 5b8a660..1fd44c3 100644
--- a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileDownloadStatus.java
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileDownloadStatus.java
@@ -22,6 +22,8 @@
 import org.eclipse.emf.emfstore.internal.server.exceptions.FileTransferException;
 import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
 
+import com.google.common.base.Optional;
+
 /**
  * An object of this class is returned from any workspace method that starts a
  * file transfer. It provides information about this file transfer and allows to
@@ -267,8 +269,8 @@
 	 *
 	 * @return the project space owning this file transfer
 	 */
-	public ProjectSpace getTransferringProjectSpace() {
-		return transferringProjectSpace;
+	public Optional<ProjectSpace> getTransferringProjectSpace() {
+		return Optional.fromNullable(transferringProjectSpace);
 	}
 
 	/**
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferCacheManager.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferCacheManager.java
index b5fbcee..0c76245 100644
--- a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferCacheManager.java
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferCacheManager.java
@@ -53,11 +53,6 @@
 	public static final String FILE_NAME_DELIMITER = "_"; //$NON-NLS-1$
 
 	/**
-	 * The associated project space.
-	 */
-	private final ProjectSpace projectSpace;
-
-	/**
 	 * The cache folder, constructed from the identifier of the project space.
 	 */
 	private final File cacheFolder;
@@ -74,13 +69,23 @@
 	 *            the project space to which this cache belongs.
 	 */
 	public FileTransferCacheManager(ProjectSpace projectSpaceImpl) {
-		projectSpace = projectSpaceImpl;
-		cacheFolder = new File(getCacheFolder(projectSpace));
+		cacheFolder = new File(getCacheFolder(projectSpaceImpl));
 		tempCacheFolder = new File(cacheFolder, "temp"); //$NON-NLS-1$
 		mkdirs();
 	}
 
 	/**
+	 * Default constructor for uploads without an available project space.
+	 */
+	public FileTransferCacheManager() {
+		cacheFolder = new File(new File(System.getProperty("java.io.tmpdir")), //$NON-NLS-1$
+			"ESFileTransferCacheManager" + System.currentTimeMillis()); //$NON-NLS-1$
+		tempCacheFolder = new File(cacheFolder, "temp"); //$NON-NLS-1$
+		mkdirs();
+		cacheFolder.deleteOnExit();
+	}
+
+	/**
 	 * Returns the default file cache folder of a given projectspace.
 	 *
 	 * @param projectSpace the projectSpace
@@ -160,6 +165,7 @@
 	public File createTempFile(FileIdentifier id) throws FileTransferException {
 		mkdirs();
 		final File cacheFile = getFileFromId(tempCacheFolder, id);
+		cacheFile.getParentFile().mkdirs();
 		if (cacheFile.exists()) {
 			cacheFile.delete();
 		}
@@ -224,6 +230,7 @@
 				Messages.FileTransferCacheManager_MoveToCacheFailed_Exists
 					+ id.getIdentifier());
 		}
+		cacheFile.getParentFile().mkdirs();
 		if (!tmpFile.renameTo(cacheFile)) {
 			throw new FileTransferException(Messages.FileTransferCacheManager_MoveToCacheFailed_MoveFailed);
 		}
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferJob.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferJob.java
index 9f7181d..483ad61 100644
--- a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferJob.java
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferJob.java
@@ -17,7 +17,6 @@
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.emf.emfstore.internal.client.model.ESWorkspaceProviderImpl;
 import org.eclipse.emf.emfstore.internal.client.model.connectionmanager.ConnectionManager;
-import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceBase;
 import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand;
 import org.eclipse.emf.emfstore.internal.server.exceptions.FileTransferException;
 import org.eclipse.emf.emfstore.internal.server.filetransfer.FilePartitionerUtil;
@@ -40,7 +39,6 @@
 	private SessionId sessionId;
 	private boolean canceled;
 
-	private final ProjectSpaceBase projectSpace;
 	private final FileTransferManager transferManager;
 	private final FileTransferCacheManager cache;
 	private final FileTransferInformation fileInformation;
@@ -57,7 +55,6 @@
 		super(name);
 		fileInformation = fileInfo;
 		fileId = fileInfo.getFileIdentifier();
-		projectSpace = transferManager.getProjectSpace();
 		this.transferManager = transferManager;
 		cache = transferManager.getCache();
 	}
@@ -70,16 +67,16 @@
 	protected void getConnectionAttributes() throws FileTransferException {
 
 		connectionManager = ESWorkspaceProviderImpl.getInstance().getConnectionManager();
-		projectId = projectSpace.getProjectId();
+		projectId = transferManager.getProjectId();
 
-		if (projectSpace.getUsersession() == null) {
+		if (transferManager.getUsersession() == null) {
 			throw new FileTransferException(Messages.FileTransferJob_UnknownSession);
 		}
 
 		new EMFStoreCommand() {
 			@Override
 			protected void doRun() {
-				sessionId = projectSpace.getUsersession().getSessionId();
+				sessionId = transferManager.getUsersession().getSessionId();
 			}
 		}.run(false);
 	}
@@ -180,15 +177,6 @@
 	}
 
 	/**
-	 * Returns the project space which is associated with the file transfer.
-	 *
-	 * @return the associated project space
-	 */
-	protected ProjectSpaceBase getProjectSpace() {
-		return projectSpace;
-	}
-
-	/**
 	 * Returns the file transfer manager administering the file transfer.
 	 *
 	 * @return the file transfer manager
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferManager.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferManager.java
index efc6cdf..74d04d9 100644
--- a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferManager.java
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/FileTransferManager.java
@@ -14,17 +14,21 @@
 import java.io.File;
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.List;
 
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
-import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.emfstore.internal.client.model.Usersession;
+import org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.ProjectSpaceUploadQueue;
+import org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.SimpleUploadQueue;
+import org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue;
 import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceBase;
 import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreClientUtil;
-import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand;
 import org.eclipse.emf.emfstore.internal.client.model.util.WorkspaceUtil;
 import org.eclipse.emf.emfstore.internal.server.exceptions.FileTransferException;
 import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
 import org.eclipse.emf.emfstore.internal.server.model.ModelFactory;
+import org.eclipse.emf.emfstore.internal.server.model.ProjectId;
 
 /**
  * The main managing class on the client side for file transfers. Each project
@@ -48,6 +52,12 @@
 	 */
 	private final ProjectSpaceBase projectSpace;
 
+	private final UploadQueue uploadQueue;
+
+	private ProjectId projectId;
+
+	private Usersession usersession;
+
 	/**
 	 * Constructor that creates a file transfer manager for a specific project
 	 * space. Only to be called in the init of a project space!
@@ -57,10 +67,25 @@
 	 */
 	public FileTransferManager(ProjectSpaceBase projectSpaceImpl) {
 		cacheManager = new FileTransferCacheManager(projectSpaceImpl);
+		uploadQueue = new ProjectSpaceUploadQueue(projectSpaceImpl);
 		projectSpace = projectSpaceImpl;
 	}
 
 	/**
+	 * Constructor that creates a file transfer manager when no project space is available.
+	 *
+	 * @param projectId the project id
+	 * @param usersession the usersession
+	 */
+	public FileTransferManager(ProjectId projectId, Usersession usersession) {
+		this.projectId = projectId;
+		this.usersession = usersession;
+		cacheManager = new FileTransferCacheManager();
+		uploadQueue = new SimpleUploadQueue();
+		projectSpace = null;
+	}
+
+	/**
 	 * Adds a file to be transferred (uploaded).
 	 *
 	 * @param file
@@ -127,20 +152,13 @@
 	 * @param identifier
 	 */
 	private void addToCommitQueue(final FileIdentifier identifier) {
-		for (final FileIdentifier f : projectSpace.getWaitingUploads()) {
+		for (final FileIdentifier f : uploadQueue.getWaitingUploads()) {
 			if (f.getIdentifier().equals(identifier.getIdentifier())) {
 				return;
 			}
 
 		}
-		new EMFStoreCommand() {
-			@Override
-			protected void doRun() {
-				projectSpace.getWaitingUploads().add(identifier);
-				projectSpace.saveProjectSpaceOnly();
-			}
-		}.run(true);
-
+		uploadQueue.add(identifier);
 	}
 
 	/**
@@ -152,7 +170,7 @@
 	 */
 	public void uploadQueuedFiles(IProgressMonitor progress) {
 		try {
-			final EList<FileIdentifier> uploads = projectSpace.getWaitingUploads();
+			final List<FileIdentifier> uploads = uploadQueue.getWaitingUploads();
 			while (!uploads.isEmpty()) {
 				final FileIdentifier fi = uploads.get(0);
 
@@ -168,13 +186,7 @@
 							fi.getIdentifier()),
 						null);
 					// Remove from commit queue
-					new EMFStoreCommand() {
-						@Override
-						protected void doRun() {
-							projectSpace.getWaitingUploads().remove(fi);
-							projectSpace.saveProjectSpaceOnly();
-						}
-					}.run(true);
+					uploadQueue.remove(fi);
 					continue;
 
 				}
@@ -226,7 +238,9 @@
 
 		// If the file is cached locally, get it
 		// if (cacheManager.hasCachedFile(fileIdentifier)) {
-		return FileDownloadStatus.Factory.createAlreadyFinished(projectSpace, fileIdentifier,
+		return FileDownloadStatus.Factory.createAlreadyFinished(
+			getProjectSpace()/* may be null */,
+			fileIdentifier,
 			cacheManager.getCachedFile(fileIdentifier));
 		// }
 
@@ -261,7 +275,9 @@
 	 * @return the status
 	 */
 	private FileDownloadStatus startDownload(FileIdentifier fileIdentifier, boolean isTriggeredByUI) {
-		final FileDownloadStatus fds = FileDownloadStatus.Factory.createNew(projectSpace, fileIdentifier);
+		final FileDownloadStatus fds = FileDownloadStatus.Factory.createNew(
+			getProjectSpace()/* may be null */,
+			fileIdentifier);
 		// TODO Check if true is correct here
 		final FileDownloadJob job = new FileDownloadJob(fds, this, fileIdentifier, isTriggeredByUI);
 		job.schedule();
@@ -297,7 +313,7 @@
 		 * the element. Equals is not well-defined for EObjects, so we cannot
 		 * use it here.
 		 */
-		for (final FileIdentifier upload : projectSpace.getWaitingUploads()) {
+		for (final FileIdentifier upload : uploadQueue.getWaitingUploads()) {
 			// This is our equals: Compare the strings!
 			if (upload.getIdentifier().equals(fileId.getIdentifier())) {
 				return i;
@@ -319,8 +335,7 @@
 	void removeWaitingUpload(FileIdentifier fileId) throws FileTransferException {
 		final int index = getWaitingUploadIndex(fileId);
 		if (index != -1) {
-			projectSpace.getWaitingUploads().remove(index);
-			projectSpace.saveProjectSpaceOnly();
+			uploadQueue.remove(index);
 
 		} else {
 			// Not found in list? exception!
@@ -382,4 +397,24 @@
 		return projectSpace;
 	}
 
+	/**
+	 * @return the {@link ProjectId}
+	 */
+	ProjectId getProjectId() {
+		if (getProjectSpace() != null) {
+			return getProjectSpace().getProjectId();
+		}
+		return projectId;
+	}
+
+	/**
+	 * @return the {@link Usersession}
+	 */
+	Usersession getUsersession() {
+		if (getProjectSpace() != null) {
+			return getProjectSpace().getUsersession();
+		}
+		return usersession;
+	}
+
 }
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/ProjectSpaceUploadQueue.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/ProjectSpaceUploadQueue.java
new file mode 100644
index 0000000..7263be4
--- /dev/null
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/ProjectSpaceUploadQueue.java
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2017 EclipseSource Muenchen GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.emfstore.internal.client.model.filetransfer.util;
+
+import java.util.List;
+
+import org.eclipse.emf.emfstore.internal.client.model.impl.ProjectSpaceBase;
+import org.eclipse.emf.emfstore.internal.client.model.util.EMFStoreCommand;
+import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
+
+/**
+ * {@link UploadQueue} implementation backed by a {@link ProjectSpaceBase}.
+ */
+public class ProjectSpaceUploadQueue implements UploadQueue {
+
+	private final ProjectSpaceBase projectSpace;
+
+	/**
+	 * @param projectSpace the backing {@link ProjectSpaceBase}
+	 */
+	public ProjectSpaceUploadQueue(ProjectSpaceBase projectSpace) {
+		this.projectSpace = projectSpace;
+	}
+
+	/**
+	 *
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#getWaitingUploads()
+	 */
+	public List<FileIdentifier> getWaitingUploads() {
+		return projectSpace.getWaitingUploads();
+	}
+
+	/**
+	 *
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#add(org.eclipse.emf.emfstore.internal.server.model.FileIdentifier)
+	 */
+	public void add(final FileIdentifier identifier) {
+		new EMFStoreCommand() {
+			@Override
+			protected void doRun() {
+				projectSpace.getWaitingUploads().add(identifier);
+				projectSpace.saveProjectSpaceOnly();
+			}
+		}.run(true);
+
+	}
+
+	/**
+	 *
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#remove(org.eclipse.emf.emfstore.internal.server.model.FileIdentifier)
+	 */
+	public void remove(final FileIdentifier identifier) {
+		new EMFStoreCommand() {
+			@Override
+			protected void doRun() {
+				projectSpace.getWaitingUploads().remove(identifier);
+				projectSpace.saveProjectSpaceOnly();
+			}
+		}.run(true);
+	}
+
+	/**
+	 * 
+	 * {@inheritDoc}
+	 * 
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#remove(int)
+	 */
+	public void remove(int index) {
+		projectSpace.getWaitingUploads().remove(index);
+		projectSpace.saveProjectSpaceOnly();
+	}
+
+}
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/SimpleUploadQueue.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/SimpleUploadQueue.java
new file mode 100644
index 0000000..c467be0
--- /dev/null
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/SimpleUploadQueue.java
@@ -0,0 +1,62 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2017 EclipseSource Muenchen GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.emfstore.internal.client.model.filetransfer.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
+
+/**
+ * Simple in memory representation of an {@link UploadQueue}.
+ */
+public class SimpleUploadQueue implements UploadQueue {
+
+	private final List<FileIdentifier> fileIdentifiers = new ArrayList<FileIdentifier>();
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#getWaitingUploads()
+	 */
+	public List<FileIdentifier> getWaitingUploads() {
+		return fileIdentifiers;
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#add(org.eclipse.emf.emfstore.internal.server.model.FileIdentifier)
+	 */
+	public void add(FileIdentifier identifier) {
+		fileIdentifiers.add(identifier);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#remove(org.eclipse.emf.emfstore.internal.server.model.FileIdentifier)
+	 */
+	public void remove(FileIdentifier identifier) {
+		fileIdentifiers.remove(identifier);
+	}
+
+	/**
+	 * {@inheritDoc}
+	 *
+	 * @see org.eclipse.emf.emfstore.internal.client.model.filetransfer.util.UploadQueue#remove(int)
+	 */
+	public void remove(int index) {
+		fileIdentifiers.remove(index);
+	}
+
+}
diff --git a/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/UploadQueue.java b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/UploadQueue.java
new file mode 100644
index 0000000..ca883f3
--- /dev/null
+++ b/bundles/org.eclipse.emf.emfstore.client/src/org/eclipse/emf/emfstore/internal/client/model/filetransfer/util/UploadQueue.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2017 EclipseSource Muenchen GmbH and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Johannes Faltermeier - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.emf.emfstore.internal.client.model.filetransfer.util;
+
+import java.util.List;
+
+import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
+
+/**
+ * A queue storing the files which will be uploaded to the server.
+ */
+public interface UploadQueue {
+
+	/**
+	 * @return the {@link FileIdentifier files} waiting to be uploaded
+	 */
+	List<FileIdentifier> getWaitingUploads();
+
+	/**
+	 * Enqueues a {@link FileIdentifier file} for upload.
+	 *
+	 * @param identifier the id of the file
+	 */
+	void add(FileIdentifier identifier);
+
+	/**
+	 * Removes a file from the upload queue.
+	 *
+	 * @param identifier the id of the file
+	 */
+	void remove(FileIdentifier identifier);
+
+	/**
+	 * Removes the file at the given index from the upload queue.
+	 * 
+	 * @param index the index of the {@link FileIdentifier file}
+	 */
+	void remove(int index);
+
+}
diff --git a/bundles/org.eclipse.emf.emfstore.server/src/org/eclipse/emf/emfstore/internal/server/core/subinterfaces/FileTransferSubInterfaceImpl.java b/bundles/org.eclipse.emf.emfstore.server/src/org/eclipse/emf/emfstore/internal/server/core/subinterfaces/FileTransferSubInterfaceImpl.java
index da3040d..9cb8ab1 100644
--- a/bundles/org.eclipse.emf.emfstore.server/src/org/eclipse/emf/emfstore/internal/server/core/subinterfaces/FileTransferSubInterfaceImpl.java
+++ b/bundles/org.eclipse.emf.emfstore.server/src/org/eclipse/emf/emfstore/internal/server/core/subinterfaces/FileTransferSubInterfaceImpl.java
@@ -123,6 +123,7 @@
 				throw new FileTransferException(
 					Messages.FileTransferSubInterfaceImpl_File_Inaccessible, e);
 			}
+			tmpFile.getParentFile().mkdirs();
 			// file reslicer for reslicing temp file
 			FilePartitionerUtil.writeChunk(tmpFile, fileChunk);
 			// move file from temp folder to attachment folder if last file chunk is received
diff --git a/tests/org.eclipse.emf.emfstore.server.test/src/org/eclipse/emf/emfstore/server/test/FileManagerTest.java b/tests/org.eclipse.emf.emfstore.server.test/src/org/eclipse/emf/emfstore/server/test/FileManagerTest.java
index 1db3c67..952eb8e 100644
--- a/tests/org.eclipse.emf.emfstore.server.test/src/org/eclipse/emf/emfstore/server/test/FileManagerTest.java
+++ b/tests/org.eclipse.emf.emfstore.server.test/src/org/eclipse/emf/emfstore/server/test/FileManagerTest.java
@@ -18,9 +18,11 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Arrays;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
+import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.emf.emfstore.client.ESServer;
 import org.eclipse.emf.emfstore.client.ESUsersession;
 import org.eclipse.emf.emfstore.client.exceptions.ESServerStartFailedException;
@@ -30,8 +32,11 @@
 import org.eclipse.emf.emfstore.client.test.common.util.ServerUtil;
 import org.eclipse.emf.emfstore.internal.client.model.filetransfer.FileDownloadStatus;
 import org.eclipse.emf.emfstore.internal.client.model.filetransfer.FileDownloadStatus.Status;
+import org.eclipse.emf.emfstore.internal.client.model.filetransfer.FileTransferManager;
 import org.eclipse.emf.emfstore.internal.server.exceptions.FatalESException;
+import org.eclipse.emf.emfstore.internal.server.exceptions.FileTransferException;
 import org.eclipse.emf.emfstore.internal.server.model.FileIdentifier;
+import org.eclipse.emf.emfstore.internal.server.model.ModelFactory;
 import org.eclipse.emf.emfstore.server.exceptions.ESException;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -133,4 +138,134 @@
 			fileInputStream2.close();
 		}
 	}
+
+	@Test
+	public void testFileUploadWithoutDirectProjectSpaceUsage()
+		throws IOException, FileTransferException, InterruptedException {
+		/* setup */
+		file = File.createTempFile("foo", "tmp"); //$NON-NLS-1$//$NON-NLS-2$
+		file.deleteOnExit();
+		FileUtils.writeStringToFile(file, System.currentTimeMillis() + "FOObar"); //$NON-NLS-1$
+
+		/* act */
+		final FileTransferManager transferManager1 = new FileTransferManager(
+			getProjectSpace1().getProjectId()/* id */,
+			getProjectSpace1().getUsersession()/* usersession */);
+		transferManager1.addFile(file, "myId"); //$NON-NLS-1$
+		transferManager1.uploadQueuedFiles(new NullProgressMonitor());
+
+		/* assert */
+		final FileTransferManager transferManager2 = new FileTransferManager(
+			getProjectSpace2().getProjectId()/* id */,
+			getProjectSpace2().getUsersession()/* usersession */);
+		final FileIdentifier fileIdentifier = ModelFactory.eINSTANCE.createFileIdentifier();
+		fileIdentifier.setIdentifier("myId"); //$NON-NLS-1$
+		final FileDownloadStatus status = transferManager2.getFile(fileIdentifier, false);
+		assertTrue(status != null);
+		// wait for file to be completely transferred
+		while (status.getStatus() != Status.FINISHED) {
+			if (status.getStatus() == Status.FAILED) {
+				fail("download failed"); //$NON-NLS-1$
+			}
+			Thread.sleep(100);
+		}
+		final File transferredFile = status.getTransferredFile(true);
+		final FileInputStream fileInputStream = new FileInputStream(file);
+		final FileInputStream fileInputStream2 = new FileInputStream(transferredFile);
+		try {
+			assertTrue(Arrays.equals(IOUtils.toByteArray(fileInputStream), IOUtils.toByteArray(fileInputStream2)));
+		} finally {
+			fileInputStream.close();
+			fileInputStream2.close();
+		}
+	}
+
+	@Test
+	public void testFileUploadWithoutDirectProjectSpaceUsageOverride()
+		throws IOException, FileTransferException, InterruptedException {
+		/* setup */
+		file = File.createTempFile("foo", "tmp"); //$NON-NLS-1$//$NON-NLS-2$
+		file.deleteOnExit();
+		FileUtils.writeStringToFile(file, System.currentTimeMillis() + "FOObar"); //$NON-NLS-1$
+		final FileTransferManager transferManager1 = new FileTransferManager(
+			getProjectSpace1().getProjectId()/* id */,
+			getProjectSpace1().getUsersession()/* usersession */);
+		transferManager1.addFile(file, "myId"); //$NON-NLS-1$
+		transferManager1.uploadQueuedFiles(new NullProgressMonitor());
+
+		/* act */
+		final File file2 = File.createTempFile("foo2", "tmp"); //$NON-NLS-1$//$NON-NLS-2$
+		file2.deleteOnExit();
+		FileUtils.writeStringToFile(file2, System.currentTimeMillis() + "FOObar2"); //$NON-NLS-1$
+		transferManager1.addFile(file2, "myId"); //$NON-NLS-1$
+		transferManager1.uploadQueuedFiles(new NullProgressMonitor());
+
+		/* assert */
+		final FileTransferManager transferManager2 = new FileTransferManager(
+			getProjectSpace2().getProjectId()/* id */,
+			getProjectSpace2().getUsersession()/* usersession */);
+		final FileIdentifier fileIdentifier = ModelFactory.eINSTANCE.createFileIdentifier();
+		fileIdentifier.setIdentifier("myId"); //$NON-NLS-1$
+		final FileDownloadStatus status = transferManager2.getFile(fileIdentifier, false);
+		assertTrue(status != null);
+		// wait for file to be completely transferred
+		while (status.getStatus() != Status.FINISHED) {
+			if (status.getStatus() == Status.FAILED) {
+				fail("download failed"); //$NON-NLS-1$
+			}
+			Thread.sleep(100);
+		}
+		final File transferredFile = status.getTransferredFile(true);
+		final FileInputStream fileInputStream = new FileInputStream(file2);
+		final FileInputStream fileInputStream2 = new FileInputStream(transferredFile);
+		try {
+			assertTrue(Arrays.equals(IOUtils.toByteArray(fileInputStream), IOUtils.toByteArray(fileInputStream2)));
+		} finally {
+			fileInputStream.close();
+			fileInputStream2.close();
+		}
+	}
+
+	@Test
+	public void testFileUploadWithoutDirectProjectSpaceUsagePathSeparatorInId()
+		throws IOException, FileTransferException, InterruptedException {
+		/* setup */
+		file = File.createTempFile("foo", "tmp"); //$NON-NLS-1$//$NON-NLS-2$
+		file.deleteOnExit();
+		FileUtils.writeStringToFile(file, System.currentTimeMillis() + "FOObar"); //$NON-NLS-1$
+
+		/* act */
+		final FileTransferManager transferManager1 = new FileTransferManager(
+			getProjectSpace1().getProjectId()/* id */,
+			getProjectSpace1().getUsersession()/* usersession */);
+		transferManager1.addFile(file, "myFolder/myId"); //$NON-NLS-1$
+		transferManager1.uploadQueuedFiles(new NullProgressMonitor());
+
+		/* assert */
+		final FileTransferManager transferManager2 = new FileTransferManager(
+			getProjectSpace2().getProjectId()/* id */,
+			getProjectSpace2().getUsersession()/* usersession */);
+		final FileIdentifier fileIdentifier = ModelFactory.eINSTANCE.createFileIdentifier();
+		fileIdentifier.setIdentifier("myFolder/myId"); //$NON-NLS-1$
+		final FileDownloadStatus status = transferManager2.getFile(fileIdentifier, false);
+		assertTrue(status != null);
+		// wait for file to be completely transferred
+		while (status.getStatus() != Status.FINISHED) {
+			if (status.getStatus() == Status.FAILED) {
+				fail("download failed"); //$NON-NLS-1$
+			}
+			Thread.sleep(100);
+		}
+		final File transferredFile = status.getTransferredFile(true);
+		final FileInputStream fileInputStream = new FileInputStream(file);
+		final FileInputStream fileInputStream2 = new FileInputStream(transferredFile);
+		try {
+			assertTrue(Arrays.equals(IOUtils.toByteArray(fileInputStream), IOUtils.toByteArray(fileInputStream2)));
+		} finally {
+			fileInputStream.close();
+			fileInputStream2.close();
+		}
+		assertEquals("myId", transferredFile.getName()); //$NON-NLS-1$
+		assertEquals("myFolder", transferredFile.getParentFile().getName()); //$NON-NLS-1$
+	}
 }