From dfe5cb2a0d511e7b5734a39bb3a777b8ce2a8390 Mon Sep 17 00:00:00 2001
From: srosse <none@none>
Date: Wed, 4 Jan 2012 17:18:08 +0100
Subject: [PATCH] OO-23: fix issues with WebDAV + Versioning + some editors
 like Word, Excel, PowerPoint, BBEdit, Notepad and Pixelmator

---
 .../core/util/servlets/VFSDirContext.java     |  21 ++-
 .../util/vfs/version/RevisionFileImpl.java    |  13 ++
 .../util/vfs/version/VersionsFileManager.java | 108 ++++++++++++-
 .../util/vfs/version/VersionsManager.java     |  18 +++
 .../core/util/vfs/VersionManagerTest.java     | 144 ++++++++++++++++++
 5 files changed, 298 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/olat/core/util/servlets/VFSDirContext.java b/src/main/java/org/olat/core/util/servlets/VFSDirContext.java
index 26321755117..a821c43b419 100644
--- a/src/main/java/org/olat/core/util/servlets/VFSDirContext.java
+++ b/src/main/java/org/olat/core/util/servlets/VFSDirContext.java
@@ -39,11 +39,17 @@ import javax.naming.Binding;
 import javax.naming.Context;
 import javax.naming.NameAlreadyBoundException;
 import javax.naming.NameClassPair;
+import javax.naming.NameNotFoundException;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
+import javax.naming.NotContextException;
 import javax.naming.OperationNotSupportedException;
+import javax.naming.directory.AttributeModificationException;
 import javax.naming.directory.Attributes;
 import javax.naming.directory.DirContext;
+import javax.naming.directory.InvalidAttributesException;
+import javax.naming.directory.InvalidSearchControlsException;
+import javax.naming.directory.InvalidSearchFilterException;
 import javax.naming.directory.ModificationItem;
 import javax.naming.directory.SearchControls;
 import javax.naming.directory.SearchResult;
@@ -581,8 +587,8 @@ public class VFSDirContext extends BaseDirContext {
 			VFSResource vfsResource = (VFSResource)obj;
 			if(vfsResource.vfsItem instanceof Versionable
 					&& ((Versionable)vfsResource.vfsItem).getVersions().isVersioned()) {
-				Versionable currentVersion = (Versionable)vfsResource.vfsItem;
-				VersionsManager.getInstance().move(currentVersion, childLeaf.getParentContainer());
+				VFSLeaf currentVersion = (VFSLeaf)vfsResource.vfsItem;
+				VersionsManager.getInstance().move(currentVersion, childLeaf, identity);
 			}
 		}
 	}
@@ -610,11 +616,16 @@ public class VFSDirContext extends BaseDirContext {
 		// Check obj type
 
 		VFSItem vfsItem = resolveFile(name);
-		if (vfsItem == null || (!(vfsItem instanceof VFSLeaf))) throw new NamingException(smgr.getString("resources.bindFailed", name));
+		if (vfsItem == null || (!(vfsItem instanceof VFSLeaf))) {
+			throw new NamingException(smgr.getString("resources.bindFailed", name));
+		}
 		VFSLeaf file = (VFSLeaf)vfsItem;
-		
 		if(file instanceof Versionable && ((Versionable)file).getVersions().isVersioned()) {
-			VersionsManager.getInstance().addToRevisions((Versionable)file, identity, "");
+			if(file.getSize() == 0) {
+				VersionsManager.getInstance().createVersionsFor(file, true);
+			} else {
+				VersionsManager.getInstance().addToRevisions((Versionable)file, identity, "");
+			}
 		}
 		
 		copyVFS(file, name, obj, attrs);
diff --git a/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java b/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
index f647fdcfa53..4f15df03c0e 100644
--- a/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
+++ b/src/main/java/org/olat/core/util/vfs/version/RevisionFileImpl.java
@@ -40,6 +40,7 @@ public class RevisionFileImpl implements VFSRevision {
 	private String author;
 	private String comment;
 	private String name;
+	private String uuid;
 	private long lastModified;
 
 	private VFSLeaf file;
@@ -53,6 +54,18 @@ public class RevisionFileImpl implements VFSRevision {
 	public RevisionFileImpl() {
 	//
 	}
+	
+	/**
+	 * UUID of the revision
+	 * @return
+	 */
+	public String getUuid() {
+		return uuid;
+	}
+
+	public void setUuid(String uuid) {
+		this.uuid = uuid;
+	}
 
 	/**
 	 * @return the file of this revision
diff --git a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
index a6f25b3410a..08e80adde87 100644
--- a/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
+++ b/src/main/java/org/olat/core/util/vfs/version/VersionsFileManager.java
@@ -38,6 +38,7 @@ import org.olat.core.configuration.Initializable;
 import org.olat.core.id.Identity;
 import org.olat.core.logging.OLog;
 import org.olat.core.logging.Tracing;
+import org.olat.core.util.StringHelper;
 import org.olat.core.util.vfs.LocalFileImpl;
 import org.olat.core.util.vfs.LocalFolderImpl;
 import org.olat.core.util.vfs.LocalImpl;
@@ -88,6 +89,11 @@ public class VersionsFileManager extends VersionsManager implements Initializabl
 
 	@Override
 	public Versions createVersionsFor(VFSLeaf leaf) {
+		return createVersionsFor(leaf, false);
+	}
+	
+	@Override
+	public Versions createVersionsFor(VFSLeaf leaf, boolean force) {
 		if (!(leaf instanceof Versionable)) {
 			return NOT_VERSIONED;
 		} else if (isVersionFile(leaf)) {
@@ -166,6 +172,84 @@ public class VersionsFileManager extends VersionsManager implements Initializabl
 		return false;
 	}
 
+	@Override
+	public boolean move(VFSLeaf currentFile, VFSLeaf targetFile, Identity author) {
+		VFSLeaf fCurrentVersions = getCanonicalVersionXmlFile(currentFile, true);
+		Versions currentVersions = readVersions(currentFile, fCurrentVersions);
+		
+		boolean brandNewVersionFile = false;
+		VFSLeaf fTargetVersions = getCanonicalVersionXmlFile(targetFile, false);
+		if(fTargetVersions == null) {
+			brandNewVersionFile = true;
+			fTargetVersions = getCanonicalVersionXmlFile(targetFile, true);
+		}
+ 
+		Versions targetVersions = readVersions(targetFile, fTargetVersions);
+		if(!(currentVersions instanceof VersionsFileImpl) || !(targetVersions instanceof  VersionsFileImpl)) {
+			return false;
+		}
+		
+		VersionsFileImpl targetVersionsImpl = (VersionsFileImpl)targetVersions;
+		if(author != null) {
+			targetVersionsImpl.setAuthor(author.getName());
+		}
+		if(brandNewVersionFile) {
+			targetVersionsImpl.setCreator(currentVersions.getCreator());
+			targetVersionsImpl.setComment(currentVersions.getComment());
+		}
+
+		boolean allOk = true;
+		for(VFSRevision revision:currentVersions.getRevisions()) {
+			allOk &= copyRevision(revision, fTargetVersions, targetVersionsImpl);
+		}
+
+		targetVersionsImpl.setRevisionNr(getNextRevisionNr(targetVersionsImpl));
+		XStreamHelper.writeObject(mystream, fTargetVersions, targetVersionsImpl);
+
+		return allOk;
+	}
+	
+	private boolean copyRevision(VFSRevision revision, VFSLeaf fNewVersions, VersionsFileImpl targetVersions) {
+		if(!(revision instanceof RevisionFileImpl)) {
+			logWarn("Copy only copy persisted revisions", null);
+		}
+		
+		RevisionFileImpl revisionImpl = (RevisionFileImpl)revision;
+		String revUuid = revisionImpl.getUuid();
+		for(VFSRevision rev:targetVersions.getRevisions()) {
+			if(rev instanceof RevisionFileImpl) {
+				RevisionFileImpl fRev = (RevisionFileImpl)rev;
+				if(StringHelper.containsNonWhitespace(fRev.getUuid()) && fRev.getUuid().equals(revUuid)) {
+					return true;
+				}
+			}
+		}
+
+		String uuid = UUID.randomUUID().toString().replace("-", "") + "_" + revision.getName();
+		
+		RevisionFileImpl newRevision = new RevisionFileImpl();
+		newRevision.setName(revision.getName());
+		newRevision.setFilename(uuid);
+		newRevision.setRevisionNr(getNextRevisionNr(targetVersions));
+		newRevision.setComment(revision.getComment());
+		newRevision.setAuthor(revision.getAuthor());
+		newRevision.setLastModified(revision.getLastModified());
+		newRevision.setUuid(revUuid);
+
+		//copy -> the files revision
+		InputStream revisionIn = revision.getInputStream();
+
+		VFSLeaf target = fNewVersions.getParentContainer().createChildLeaf(uuid);
+		if (VFSManager.copyContent(revisionIn, target)) {
+			targetVersions.setComment(revision.getComment());
+			targetVersions.getRevisions().add(newRevision);
+			targetVersions.setRevisionNr(getNextRevisionNr(targetVersions));
+			targetVersions.setAuthor(revision.getAuthor());
+			return true;
+		}
+		return false;
+	}
+
 	@Override
 	public boolean move(Versionable currentVersion, VFSContainer container) {
 		VFSLeaf currentFile = (VFSLeaf) currentVersion;
@@ -299,7 +383,7 @@ public class VersionsFileManager extends VersionsManager implements Initializabl
 			return true;
 		} else if (item instanceof VFSLeaf && item instanceof Versionable) {
 			VFSLeaf leaf = (VFSLeaf)item;
-			if (force) {
+			if (force || isTemporaryFile(leaf)) {
 				cleanUp(leaf);
 			} else {
 				addToRevisions((Versionable)leaf, null, null);
@@ -308,6 +392,27 @@ public class VersionsFileManager extends VersionsManager implements Initializabl
 		return false;
 	}
 	
+	/**
+	 * Some temporary files of specific editors need to be force deleted
+	 * with all versions. Word can reuse older names.
+	 * @param leaf
+	 * @return
+	 */
+	private boolean isTemporaryFile(VFSLeaf leaf) {
+		String name = leaf.getName();
+		//temporary files of Word 2010: ~WRD0002.tmp
+		if((name.startsWith("~WRD") || name.startsWith("~WRL")) && name.endsWith(".tmp")) {
+			return true;
+		}
+		//temporary files of PowerPoint 2010: ppt5101.tmp 
+		if(name.startsWith("ppt") && name.endsWith(".tmp")) {
+			return true;
+		}
+		//OpenOffice Text: .~lock.Versions_21.odt#
+		
+		return false;
+	}
+	
 	/**
 	 * Clean up all revisions files, xml file
 	 * @param leaf
@@ -400,6 +505,7 @@ public class VersionsFileManager extends VersionsManager implements Initializabl
 		}
 
 		RevisionFileImpl newRevision = new RevisionFileImpl();
+		newRevision.setUuid(UUID.randomUUID().toString());
 		newRevision.setName(name);
 		newRevision.setFilename(uuid);
 		newRevision.setRevisionNr(versionNr);
diff --git a/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java b/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java
index 49ee43e9c53..2cced4eaaad 100644
--- a/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java
+++ b/src/main/java/org/olat/core/util/vfs/version/VersionsManager.java
@@ -53,6 +53,15 @@ public abstract class VersionsManager extends BasicManager {
 	 * @return
 	 */
 	public abstract Versions createVersionsFor(VFSLeaf leaf);
+	
+	/**
+	 * Get or create the versions datas of this file
+	 * 
+	 * @param a file
+	 * @param force the creation of the file
+	 * @return
+	 */
+	public abstract Versions createVersionsFor(VFSLeaf leaf, boolean force);
 
 	/**
 	 * Return the list of deleted files in this container.
@@ -92,6 +101,15 @@ public abstract class VersionsManager extends BasicManager {
 	 * @return
 	 */
 	public abstract boolean move(Versionable currentVersion, VFSContainer container);
+	
+	/**
+	 * Move a versioned file to an other (WebDAV only!!!)
+	 * 
+	 * @param currentVersion
+	 * @param oldVersion
+	 * @return
+	 */
+	public abstract boolean move(VFSLeaf currentFile, VFSLeaf targetFile, Identity author);
 
 	/**
 	 * Restore a versioned file to the selected revision. The current version is
diff --git a/src/test/java/org/olat/core/util/vfs/VersionManagerTest.java b/src/test/java/org/olat/core/util/vfs/VersionManagerTest.java
index 803cd5f9fbe..01288895d97 100644
--- a/src/test/java/org/olat/core/util/vfs/VersionManagerTest.java
+++ b/src/test/java/org/olat/core/util/vfs/VersionManagerTest.java
@@ -26,6 +26,7 @@ import org.olat.core.util.vfs.version.VFSRevision;
 import org.olat.core.util.vfs.version.Versionable;
 import org.olat.core.util.vfs.version.Versions;
 import org.olat.core.util.vfs.version.VersionsFileManager;
+import org.olat.core.util.vfs.version.VersionsManager;
 import org.olat.test.JunitTestHelper;
 import org.olat.test.OlatTestCase;
 
@@ -173,6 +174,149 @@ public class VersionManagerTest extends OlatTestCase {
 		assertEquals(id2.getName(), versions.getAuthor());
 	}
 	
+	@Test
+	public void testMove() throws IOException {
+		//create a file
+		OlatRootFolderImpl rootTest = new OlatRootFolderImpl("/test2", null);
+		String filename = getRandomName();
+		VFSLeaf file = rootTest.createChildLeaf(filename);
+		OutputStream out = file.getOutputStream(false);
+		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
+		int byteCopied = IOUtils.copy(in, out);
+		IOUtils.closeQuietly(in);
+		assertFalse(byteCopied == 0);
+		assertTrue(file instanceof Versionable);
+		assertTrue(file instanceof MetaTagged);
+		
+		//set the author
+		MetaTagged metaTagged = (MetaTagged)file;
+		MetaInfo metaInfo = metaTagged.getMetaInfo();
+		metaInfo.setAuthor(id1.getName());
+		metaInfo.setCreator(id1.getName());
+		metaInfo.write();
+		
+		//save a first version -> id2
+		Versionable versionedFile1 = (Versionable)file;
+		InputStream in1 = new ByteArrayInputStream("Hello version 1".getBytes());
+		versionedFile1.getVersions().addVersion(id2, "Version 1", in1);
+		IOUtils.closeQuietly(in1);
+		
+		//save a second version -> id1
+		Versionable versionedFile2 = (Versionable)file;
+		InputStream in2 = new ByteArrayInputStream("Hello version 2".getBytes());
+		versionedFile2.getVersions().addVersion(id1, "Version 2", in2);
+		IOUtils.closeQuietly(in2);
+		
+		//move the file
+		VFSLeaf retrievedLeaf = (VFSLeaf)rootTest.resolve(filename);
+		String copyFilename = getRandomName();
+		VFSLeaf copyFile = rootTest.createChildLeaf(copyFilename);
+		OutputStream copyOutput = copyFile.getOutputStream(false);
+		InputStream copyInput = retrievedLeaf.getInputStream();
+		IOUtils.copy(copyInput, copyOutput);
+		IOUtils.closeQuietly(copyOutput);
+		IOUtils.closeQuietly(copyInput);
+		//move the revisions
+		VersionsManager.getInstance().move(retrievedLeaf, copyFile, id2);
+		
+		//check if the revisions are moved
+		VFSLeaf retirevedCopyFile = (VFSLeaf)rootTest.resolve(copyFilename);
+		assertTrue(retirevedCopyFile instanceof Versionable);
+		Versions versions = VersionsFileManager.getInstance().createVersionsFor(retirevedCopyFile);	
+		List<VFSRevision> revisions = versions.getRevisions();
+		assertNotNull(revisions);
+		assertEquals(2, revisions.size());
+		
+		VFSRevision revision0 = revisions.get(0);
+		//we don't set an author for the original file
+		assertEquals(id1.getName(), revision0.getAuthor());
+		VFSRevision revision1 = revisions.get(1);
+		assertEquals(id2.getName(), revision1.getAuthor());
+		//current
+		assertEquals(id1.getName(), versions.getCreator());
+		assertEquals(id2.getName(), versions.getAuthor());
+	}
+	
+	/**
+	 * Create a file with 2 revision, move it to another name, move it to the primitive name:
+	 * File A, change file A, change file A, move to file B, move to file A
+	 * @throws IOException
+	 */
+	@Test
+	public void testCircleMove() throws IOException {
+		//create a file A
+		OlatRootFolderImpl rootTest = new OlatRootFolderImpl("/test2", null);
+		String filename = getRandomName();
+		VFSLeaf file = rootTest.createChildLeaf(filename);
+		OutputStream out = file.getOutputStream(false);
+		InputStream in = VersionManagerTest.class.getResourceAsStream("test.txt");
+		int byteCopied = IOUtils.copy(in, out);
+		IOUtils.closeQuietly(in);
+		assertFalse(byteCopied == 0);
+		assertTrue(file instanceof Versionable);
+		assertTrue(file instanceof MetaTagged);
+		
+		//set the author
+		MetaTagged metaTagged = (MetaTagged)file;
+		MetaInfo metaInfo = metaTagged.getMetaInfo();
+		metaInfo.setAuthor(id1.getName());
+		metaInfo.setCreator(id1.getName());
+		metaInfo.write();
+		
+		//save a first version of file A -> id2
+		Versionable versionedFile1 = (Versionable)file;
+		InputStream in1 = new ByteArrayInputStream("Hello version 1".getBytes());
+		versionedFile1.getVersions().addVersion(id2, "Version 1", in1);
+		IOUtils.closeQuietly(in1);
+		
+		//save a second version of file A -> id1
+		Versionable versionedFile2 = (Versionable)file;
+		InputStream in2 = new ByteArrayInputStream("Hello version 2".getBytes());
+		versionedFile2.getVersions().addVersion(id1, "Version 2", in2);
+		IOUtils.closeQuietly(in2);
+		
+		//move the file A -> file B
+		VFSLeaf retrievedLeaf = (VFSLeaf)rootTest.resolve(filename);
+		String copyFilename = getRandomName();
+		VFSLeaf copyFile = rootTest.createChildLeaf(copyFilename);
+		OutputStream copyOutput = copyFile.getOutputStream(false);
+		InputStream copyInput = retrievedLeaf.getInputStream();
+		IOUtils.copy(copyInput, copyOutput);
+		IOUtils.closeQuietly(copyOutput);
+		IOUtils.closeQuietly(copyInput);
+		//move the revisions
+		VersionsManager.getInstance().move(retrievedLeaf, copyFile, id2);
+		
+		//move the file B -> file A
+		VFSLeaf retrievedCopyLeaf = (VFSLeaf)rootTest.resolve(copyFilename);
+		VFSLeaf originalFile = (VFSLeaf)rootTest.resolve(filename);
+		OutputStream originalOutput = originalFile.getOutputStream(false);
+		InputStream retrievedCopyInput = retrievedCopyLeaf.getInputStream();
+		IOUtils.copy(retrievedCopyInput, originalOutput);
+		IOUtils.closeQuietly(originalOutput);
+		IOUtils.closeQuietly(retrievedCopyInput);
+		//move the revisions
+		VersionsManager.getInstance().move(retrievedCopyLeaf, originalFile, id2);
+		
+		
+		//check if the revisions are moved
+		VFSLeaf retirevedOriginalFile = (VFSLeaf)rootTest.resolve(filename);
+		assertTrue(retirevedOriginalFile instanceof Versionable);
+		Versions versions = VersionsFileManager.getInstance().createVersionsFor(retirevedOriginalFile);	
+		List<VFSRevision> revisions = versions.getRevisions();
+		assertNotNull(revisions);
+		assertEquals(2, revisions.size());
+		
+		VFSRevision revision0 = revisions.get(0);
+		//we don't set an author for the original file
+		assertEquals(id1.getName(), revision0.getAuthor());
+		VFSRevision revision1 = revisions.get(1);
+		assertEquals(id2.getName(), revision1.getAuthor());
+		//current
+		assertEquals(id1.getName(), versions.getCreator());
+		assertEquals(id2.getName(), versions.getAuthor());
+	}
+	
 	private String getRandomName() {
 		return UUID.randomUUID().toString().replace("-", "");
 	}
-- 
GitLab