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