/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.core.util.vfs.version;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.zip.Adler32;
import java.util.zip.Checksum;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.olat.core.commons.modules.bc.FolderConfig;
import org.olat.core.commons.modules.bc.meta.MetaInfo;
import org.olat.core.commons.modules.bc.meta.tagged.MetaTagged;
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.logging.activity.ThreadLocalUserActivityLogger;
import org.olat.core.util.StringHelper;
import org.olat.core.util.async.ProgressDelegate;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.LocalImpl;
import org.olat.core.util.vfs.MergeSource;
import org.olat.core.util.vfs.NamedContainerImpl;
import org.olat.core.util.vfs.OlatRelPathImpl;
import org.olat.core.util.vfs.VFSConstants;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSManager;
import org.olat.core.util.vfs.filters.SystemItemFilter;
import org.olat.core.util.vfs.filters.VFSItemSuffixFilter;
import org.olat.core.util.vfs.filters.VFSLeafFilter;
import org.olat.core.util.xml.XStreamHelper;
import com.thoughtworks.xstream.XStream;
/**
*
* Description:<br>
* This implementation of the VersionsManager saved the revisions of a file in a
* file with the same name as the original + ".xml". This xml file is saved in
* a parallel folder .version under the root defined in FolderConfig. Every revision'file
* have a name made of a generated unique id + the name of the original file.
*
* <P>
* Initial Date: 21 sept. 2009 <br>
*
* @author srosse
*/
public class VersionsFileManager extends VersionsManager implements Initializable {
private static final OLog log = Tracing.createLoggerFor(VersionsFileManager.class);
private static final Versions NOT_VERSIONED = new NotVersioned();
private static final Pattern TAG_PATTERN = Pattern.compile("\\s*[<>]\\s*");
private static XStream mystream;
private File rootFolder;
private File rootVersionFolder;
private VFSContainer rootVersionsContainer;
private FolderVersioningConfigurator versioningConfigurator;
/**
* [spring]
*/
private VersionsFileManager() {
INSTANCE = this;
}
/**
* [used by Spring]
* @param versioningConfigurator
*/
public void setVersioningConfigurator(FolderVersioningConfigurator versioningConfigurator) {
this.versioningConfigurator = versioningConfigurator;
}
@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)) {
return NOT_VERSIONED;
}
Versions versions = readVersions(leaf, false);
return versions;
}
@Override
public List<Versions> getDeletedFiles(VFSContainer container) {
List<Versions> deletedRevisions = new ArrayList<Versions>();
VFSContainer versionContainer = getCanonicalVersionFolder(container, false);
if (versionContainer != null) {
Set<String> currentNames = new HashSet<String>();
for (VFSItem item : container.getItems(new VFSLeafFilter())) {
currentNames.add(item.getName() + ".xml");
}
List<VFSItem> versionItems = versionContainer.getItems(new VFSItemSuffixFilter(new String[] { "xml" }));
for (VFSItem versionItem : versionItems) {
String name = versionItem.getName();
if (versionItem instanceof VFSLeaf && !currentNames.contains(name) && isVersionsXmlFile((VFSLeaf)versionItem)) {
Versions versions = readVersions(null, (VFSLeaf) versionItem);
if(versions != null) {
List<VFSRevision> revisions = versions.getRevisions();
if (!revisions.isEmpty()) {
deletedRevisions.add(versions);
}
}
}
}
}
return deletedRevisions;
}
private Versions readVersions(VFSLeaf leaf, boolean create) {
VFSLeaf fVersions = getCanonicalVersionXmlFile(leaf, create);
if (!create && fVersions == null) {
VersionsFileImpl versions = new VersionsFileImpl();
versions.setCurrentVersion((Versionable) leaf);
versions.setVersioned(isVersioned(leaf));
versions.setRevisionNr(getNextRevisionNr(versions));
return versions;
}
return readVersions(leaf, fVersions);
}
private boolean isVersionsXmlFile(VFSLeaf fVersions) {
if(fVersions == null || !fVersions.exists()) {
return false;
}
InputStream in = fVersions.getInputStream();
if(in == null) {
return false;
}
Scanner scanner = new Scanner(in);
scanner.useDelimiter(TAG_PATTERN);
boolean foundVersionsTag = false;
while (scanner.hasNext()) {
String tag = scanner.next();
if("versions".equals(tag)) {
foundVersionsTag = true;
break;
}
}
scanner.close();
IOUtils.closeQuietly(in);
return foundVersionsTag;
}
private Versions readVersions(VFSLeaf leaf, VFSLeaf fVersions) {
if (fVersions == null) { return new NotVersioned(); }
try {
VFSContainer fVersionContainer = fVersions.getParentContainer();
VersionsFileImpl versions = (VersionsFileImpl) XStreamHelper.readObject(mystream, fVersions);
versions.setVersionFile(fVersions);
versions.setCurrentVersion((Versionable) leaf);
if (versions.getRevisionNr() == null || versions.getRevisionNr().length() == 0) {
versions.setRevisionNr(getNextRevisionNr(versions));
}
for (VFSRevision revision : versions.getRevisions()) {
RevisionFileImpl revisionImpl = (RevisionFileImpl) revision;
revisionImpl.setContainer(fVersionContainer);
}
return versions;
} catch (Exception e) {
log.warn("This file is not a versions XML file: " + fVersions, e);
fVersions.delete();
VersionsFileImpl versions = new VersionsFileImpl();
versions.setCurrentVersion((Versionable) leaf);
versions.setVersioned(isVersioned(leaf));
versions.setRevisionNr(getNextRevisionNr(versions));
log.warn("Deleted corrupt version XML file and created new version XML file: " + versions);
// the old revisions can not be restored automatically. They are still on disk, you could recover them
// manually. This is not a perfect solution, but at least the user does not get an RS
return versions;
}
}
@Override
public boolean addVersion(Versionable currentVersion, Identity identity, String comment, InputStream newFile) {
VFSLeaf currentFile = (VFSLeaf) currentVersion;
if (addToRevisions(currentVersion, identity, comment)) {
// copy the content of the new file to the old
boolean closeInputStream = !(newFile instanceof net.sf.jazzlib.ZipInputStream || newFile instanceof java.util.zip.ZipInputStream);
if (VFSManager.copyContent(newFile, currentFile, closeInputStream)) {
return true;
}
} else {
log.error("Cannot create a version of this file: " + currentVersion);
}
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;
VFSLeaf fVersions = getCanonicalVersionXmlFile(currentFile, true);
Versions versions = readVersions(currentFile, fVersions);
VFSContainer versionContainer = getCanonicalVersionFolder(container, true);
boolean allOk = VFSConstants.YES.equals(versionContainer.copyFrom(fVersions));
for (VFSRevision revision : versions.getRevisions()) {
RevisionFileImpl revisionImpl = (RevisionFileImpl) revision;
VFSLeaf revisionFile = revisionImpl.getFile();
if (revisionFile != null) {
allOk &= VFSConstants.YES.equals(versionContainer.copyFrom(revisionFile));
}
}
allOk &= VFSConstants.YES.equals(fVersions.delete());
for (VFSRevision revision : versions.getRevisions()) {
VFSLeaf revisionFile = ((RevisionFileImpl) revision).getFile();
if (revisionFile != null) {
allOk &= VFSConstants.YES.equals(revisionFile.delete());
}
}
return allOk;
}
@Override
public boolean restore(Versionable currentVersion, VFSRevision version, String comment) {
VFSLeaf currentFile = (VFSLeaf) currentVersion;
if(!VFSManager.exists(currentFile)) {
return false;
}
// add current version to versions file
if (addToRevisions(currentVersion, null, comment)) {
// copy the content of the new file to the old
if (VFSManager.copyContent(version.getInputStream(), currentFile)) { return true; }
} else {
log.error("Cannot create a version of this file: " + currentVersion);
}
return false;
}
@Override
public boolean restore(VFSContainer container, VFSRevision revision) {
String filename = revision.getName();
VFSItem restoredItem = container.resolve(filename);
if (restoredItem == null) {
restoredItem = container.createChildLeaf(filename);
}
if (restoredItem instanceof VFSLeaf) {
VFSLeaf restoredLeaf = (VFSLeaf) restoredItem;
InputStream inStream = revision.getInputStream();
if (VFSManager.copyContent(inStream, restoredLeaf)) {
VFSLeaf versionFile = getCanonicalVersionXmlFile(restoredLeaf, true);
Versions versions = readVersions(restoredLeaf, versionFile);
if (versions instanceof VersionsFileImpl) {
versions.getRevisions().remove(revision);
((VersionsFileImpl) versions).setRevisionNr(getNextRevisionNr(versions));
}
XStreamHelper.writeObject(mystream, versionFile, versions);
return true;
}
}
return false;
}
@Override
public boolean deleteRevisions(Versionable currentVersion, List<VFSRevision> versionsToDelete) {
VFSLeaf currentFile = (VFSLeaf) currentVersion;
Versions versions = readVersions(currentFile, true);
List<VFSRevision> allVersions = versions.getRevisions();
Map<String,VFSLeaf> filenamesToDelete = new HashMap<String,VFSLeaf>(allVersions.size());
for (VFSRevision versionToDelete : versionsToDelete) {
RevisionFileImpl versionImpl = (RevisionFileImpl) versionToDelete;
for (Iterator<VFSRevision> allVersionIt = allVersions.iterator(); allVersionIt.hasNext();) {
RevisionFileImpl allVersionImpl = (RevisionFileImpl) allVersionIt.next();
if (allVersionImpl.getFilename() != null && allVersionImpl.getFilename().equals(versionImpl.getFilename())) {
allVersionIt.remove();
break;
}
}
VFSLeaf fileToDelete = versionImpl.getFile();
if (fileToDelete != null) {
filenamesToDelete.put(fileToDelete.getName(), fileToDelete);
}
}
List<RevisionFileImpl> missingFiles = new ArrayList<>();
for(VFSRevision survivingVersion:allVersions) {
RevisionFileImpl survivingVersionImpl = (RevisionFileImpl)survivingVersion;
VFSLeaf revFile = survivingVersionImpl.getFile();
if(revFile == null) {
missingFiles.add(survivingVersionImpl);//file is missing
} else if(filenamesToDelete.containsKey(revFile.getName())) {
filenamesToDelete.remove(revFile.getName());
}
}
if(missingFiles.size() > 0) {
allVersions.removeAll(missingFiles);
}
for(VFSLeaf fileToDelete:filenamesToDelete.values()) {
fileToDelete.deleteSilently();
}
VFSLeaf versionFile = getCanonicalVersionXmlFile(currentFile, true);
XStreamHelper.writeObject(mystream, versionFile, versions);
if (currentVersion.getVersions() instanceof VersionsFileImpl) {
((VersionsFileImpl) currentVersion.getVersions()).update(versions);
}
return true;
}
@Override
public boolean deleteVersions(List<Versions> versions) {
for(Versions versionToDelete:versions) {
if(versionToDelete instanceof VersionsFileImpl) {
VersionsFileImpl versionsImpl = (VersionsFileImpl)versionToDelete;
VFSLeaf versionFile = versionsImpl.getVersionFile();
if(versionFile != null) {
//robust against manual file system manipulation
versionFile.deleteSilently();
}
for (VFSRevision revisionToDelete : versionsImpl.getRevisions()) {
RevisionFileImpl versionImpl = (RevisionFileImpl)revisionToDelete;
VFSLeaf fileToDelete = versionImpl.getFile();
if (fileToDelete != null) {
fileToDelete.deleteSilently();
}
}
}
}
return true;
}
@Override
public boolean delete(VFSItem item, boolean force) {
if (item instanceof VFSContainer) {
if (force) {
VFSContainer container = (VFSContainer)item;
VFSContainer versionContainer = getCanonicalVersionFolder(container, false);
if (versionContainer == null) { return true; }
return VFSConstants.YES.equals(versionContainer.delete());
}
return true;
} else if (item instanceof VFSLeaf && item instanceof Versionable) {
VFSLeaf leaf = (VFSLeaf)item;
if (force || isTemporaryFile(leaf)) {
cleanUp(leaf);
} else {
Identity identity = ThreadLocalUserActivityLogger.getLoggedIdentity();
addToRevisions((Versionable)leaf, identity, null);
}
}
return false;
}
/**
* Some temporary/lock 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
if(name.endsWith(".tmp")) {
//Word 2010: ~WRD0002.tmp
if(name.startsWith("~WRD") || name.startsWith("~WRL")) {
return true;
}
//PowerPoint 2010: ppt5101.tmp
if(name.startsWith("ppt")) {
return true;
}
}
//lock files of Word 2010, Excel 2010, PowerPoint 2010:
if(name.startsWith("~$") && (name.endsWith(".docx") || name.endsWith(".xlsx") || name.endsWith(".pptx"))) {
return true;
}
//OpenOffice locks: .~lock.Versions_21.odt#
if(name.startsWith(".~lock.") && (name.endsWith(".odt#") /* Writer */ || name.endsWith(".ods#") /* Calc */
|| name.endsWith(".odp#") /* Impress */ || name.endsWith("odf#") /* Math */
|| name.endsWith(".odg#") /* Draw */)) {
return true;
}
//OpenOffice database lock
if(name.endsWith(".odb.lck")) {
return true;
}
return false;
}
/**
* Clean up all revisions files, xml file
* @param leaf
*/
private void cleanUp(VFSLeaf leaf) {
String relPath = getRelPath(leaf);
if (relPath == null) return; // cannot handle
File fVersion = new File(getRootVersionsFile(), relPath + ".xml");
File fParentVersion = fVersion.getParentFile();
if (!fParentVersion.exists()) return; //already deleted
VFSLeaf versionLeaf = null;
if (fVersion.exists()) {
LocalFolderImpl localVersionContainer = new LocalFolderImpl(fParentVersion);
versionLeaf = (VFSLeaf) localVersionContainer.resolve(fVersion.getName());
}
if (versionLeaf == null) return; //already deleted
Versions versions = readVersions(leaf, versionLeaf);
for (VFSRevision versionToDelete : versions.getRevisions()) {
RevisionFileImpl versionImpl = (RevisionFileImpl) versionToDelete;
VFSLeaf fileToDelete = versionImpl.getFile();
if (fileToDelete != null) {
fileToDelete.delete();
}
}
versionLeaf.delete();
}
@Override
public boolean rename(VFSItem item, String newname) {
if (item instanceof VFSLeaf) {
VFSLeaf currentFile = (VFSLeaf) item;
VFSLeaf versionFile = getCanonicalVersionXmlFile(currentFile, true);
// infinite loop if rename is own versions file
return VFSConstants.YES.equals(versionFile.rename(newname + ".xml"));
} else if (item instanceof VFSContainer) {
VFSContainer container = (VFSContainer) item;
VFSContainer versionContainer = getCanonicalVersionFolder(container, false);
if (versionContainer == null) { return true; }
return VFSConstants.YES.equals(versionContainer.rename(newname));
}
return false;
}
/**
* @see org.olat.core.util.vfs.version.VersionsManager#addToRevisions(org.olat.core.util.vfs.version.Versionable, org.olat.core.id.Identity, java.lang.String)
*/
@Override
public boolean addToRevisions(Versionable currentVersion, Identity identity, String comment) {
int maxNumOfVersions = versioningConfigurator.getMaxNumOfVersionsAllowed();
if(maxNumOfVersions == 0) {
return true;//deactivated, return all ok
}
VFSLeaf currentFile = (VFSLeaf) currentVersion;
VFSLeaf versionFile = getCanonicalVersionXmlFile(currentFile, true);
if(versionFile == null) {
return false;//cannot do something with the current file
}
VFSContainer versionContainer = versionFile.getParentContainer();
String name = currentFile.getName();
// read from the
Versions v = readVersions(currentFile, versionFile);
if (!(v instanceof VersionsFileImpl)) {
log.error("Wrong implementation of Versions: " + v);
return false;
}
VersionsFileImpl versions = (VersionsFileImpl) v;
boolean sameFile = isSameFile(currentFile, versions);
String uuid = sameFile ? getLastRevisionFilename(versions)
: UUID.randomUUID().toString() + "_" + name;
String versionNr = getNextRevisionNr(versions);
String currentAuthor = versions.getAuthor();
long lastModifiedDate = 0;
if (currentFile instanceof MetaTagged) {
MetaInfo metaInfo = ((MetaTagged) currentFile).getMetaInfo();
if(metaInfo != null) {
metaInfo.clearThumbnails();
if(currentAuthor == null) {
currentAuthor = metaInfo.getAuthor();
}
lastModifiedDate = metaInfo.getLastModified();
}
}
if(lastModifiedDate <= 0) {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
lastModifiedDate = cal.getTimeInMillis();
}
RevisionFileImpl newRevision = new RevisionFileImpl();
newRevision.setUuid(UUID.randomUUID().toString());
newRevision.setName(name);
newRevision.setFilename(uuid);
newRevision.setRevisionNr(versionNr);
newRevision.setComment(versions.getComment());
newRevision.setAuthor(currentAuthor);
newRevision.setLastModified(lastModifiedDate);
if (versions.getRevisions().isEmpty() && currentVersion instanceof MetaTagged) {
MetaTagged metaTagged = (MetaTagged) currentVersion;
versions.setCreator(metaTagged.getMetaInfo().getAuthor());
}
if (sameFile || VFSManager.copyContent(currentFile, versionContainer.createChildLeaf(uuid))) {
if (identity != null) {
versions.setAuthor(identity.getName());
}
if(maxNumOfVersions >= 0 && versions.getRevisions().size() >= maxNumOfVersions) {
List<VFSRevision> revisions = versions.getRevisions();
int numOfVersionsToDelete = Math.min(revisions.size(), (revisions.size() - maxNumOfVersions) + 1);
if(numOfVersionsToDelete > 0) {
List<VFSRevision> versionsToDelete = revisions.subList(0, numOfVersionsToDelete);
deleteRevisions(currentVersion, versionsToDelete);
versions = (VersionsFileImpl)currentVersion.getVersions();
}
}
versions.setComment(comment);
versions.getRevisions().add(newRevision);
versions.setRevisionNr(getNextRevisionNr(versions));
XStreamHelper.writeObject(mystream, versionFile, versions);
if (currentVersion.getVersions() instanceof VersionsFileImpl) {
((VersionsFileImpl) currentVersion.getVersions()).update(versions);
}
return true;
} else {
log.error("Cannot create a version of this file: " + currentVersion);
}
return false;
}
private boolean isSameFile(VFSLeaf currentFile, VersionsFileImpl versions) {
boolean same = false;
if(versions.getRevisions() != null && !versions.getRevisions().isEmpty()) {
VFSRevision lastRevision = versions.getRevisions().get(versions.getRevisions().size() -1);
long lastSize = lastRevision.getSize();
long currentSize = currentFile.getSize();
if(currentSize == lastSize && currentSize > 0
&& lastRevision instanceof RevisionFileImpl
&& currentFile instanceof LocalFileImpl) {
RevisionFileImpl lastRev = ((RevisionFileImpl)lastRevision);
LocalFileImpl current = (LocalFileImpl)currentFile;
//can be the same file
try {
Checksum cm1 = FileUtils.checksum(((LocalFileImpl)lastRev.getFile()).getBasefile() , new Adler32());
Checksum cm2 = FileUtils.checksum(current.getBasefile() , new Adler32());
same = cm1.getValue() == cm2.getValue();
} catch (IOException e) {
log.debug("Error calculating the checksum of files");
}
}
}
return same;
}
public String getNextRevisionNr(Versions versions) {
int maxNumber = 0;
for (VFSRevision version : versions.getRevisions()) {
String versionNr = version.getRevisionNr();
if (versionNr != null && versionNr.length() > 0) {
try {
int number = Integer.parseInt(versionNr);
maxNumber = Math.max(maxNumber, number);
} catch (Exception ex) {
// if not a number, don't interest us
}
}
}
return Integer.toString(maxNumber + 1);
}
private String getLastRevisionFilename(Versions versions) {
if(versions.getRevisions() == null || versions.getRevisions().isEmpty()) {
return null;
}
VFSRevision revision = versions.getRevisions().get(versions.getRevisions().size() - 1);
if(revision instanceof RevisionFileImpl) {
return ((RevisionFileImpl)revision).getFilename();
}
return null;
}
/**
* Get the canonical path to the file's meta file.
*
* @param bcPath
* @return String
*/
private VFSLeaf getCanonicalVersionXmlFile(VFSItem item, boolean create) {
File f = getOriginFile(item);
if (!f.exists()) { return null; }
String relPath = getRelPath(item);
if (relPath == null) {
// cannot handle
return null;
}
File fVersion = new File(getRootVersionsFile(), relPath + ".xml");
File fParentVersion = fVersion.getParentFile();
if (!fParentVersion.exists() && create) {
fParentVersion.mkdirs();
}
if (fVersion.exists()) {
LocalFolderImpl localVersionContainer = new LocalFolderImpl(fParentVersion);
return (VFSLeaf) localVersionContainer.resolve(fVersion.getName());
} else if (create) {
LocalFolderImpl localVersionContainer = new LocalFolderImpl(fParentVersion);
VersionsFileImpl versions = new VersionsFileImpl();
versions.setVersioned(isVersioned(item));
versions.setRevisionNr(getNextRevisionNr(versions));
VFSLeaf fVersions = localVersionContainer.createChildLeaf(fVersion.getName());
XStreamHelper.writeObject(mystream, fVersions, versions);
return fVersions;
}
return null;
}
protected VFSContainer getCanonicalVersionFolder(VFSContainer container, boolean create) {
String relPath = getRelPath(container);
File fVersion = new File(getRootVersionsFile(), relPath);
if (fVersion.exists()) { return new LocalFolderImpl(fVersion); }
if (create) {
fVersion.mkdirs();
return new LocalFolderImpl(fVersion);
}
return null;
}
private String getRelPath(VFSItem item) {
String relPath = null;
if (item instanceof NamedContainerImpl) {
item = ((NamedContainerImpl)item).getDelegate();
}
if (item instanceof MergeSource) {
item = ((MergeSource)item).getRootWriteContainer();
}
if (item instanceof OlatRelPathImpl) {
relPath = ((OlatRelPathImpl) item).getRelPath();
} else if (item instanceof LocalImpl) {
LocalImpl impl = (LocalImpl) item;
String absolutPath = impl.getBasefile().getAbsolutePath();
if (absolutPath.startsWith(getCanonicalRoot())) {
relPath = absolutPath.substring(getCanonicalRoot().length());
}
Path path = impl.getBasefile().toPath();
Path relativePath = getCanonicalRootFile().toPath().relativize(path);
String relPath2 = "/" + relativePath.toString();
log.debug(relPath + " :: " + relPath2);
}
return relPath;
}
private boolean isVersionFile(VFSItem item) {
File f = getOriginFile(item);
if (f == null) return false;
try {
String path = f.getCanonicalPath();
String vPath = getRootVersionsFile().getCanonicalPath();
return path.startsWith(vPath);
} catch (IOException e) {
log.error("Cannot check if this file is a version file: " + item, e);
return false;
}
}
private boolean isVersioned(VFSItem item) {
if (item == null) return false;
VFSContainer parent = item.getParentContainer();
return FolderConfig.versionsEnabled(parent);
}
private File getOriginFile(VFSItem item) {
if (item instanceof LocalImpl) {
LocalImpl localImpl = (LocalImpl) item;
return localImpl.getBasefile();
}
if (item instanceof OlatRelPathImpl) {
OlatRelPathImpl relPath = (OlatRelPathImpl) item;
return new File(getCanonicalRoot(), relPath.getRelPath());
}
return null;
}
public File getCanonicalRootFile() {
if(rootFolder == null) {
rootFolder = new File(FolderConfig.getCanonicalRoot());
}
return rootFolder;
}
public String getCanonicalRoot() {
return getCanonicalRootFile().getAbsolutePath();
}
public File getRootVersionsFile() {
if (rootVersionsContainer == null) {
rootVersionFolder = new File(FolderConfig.getCanonicalVersionRoot());
if(!rootVersionFolder.exists()) {
rootVersionFolder.mkdirs();
}
rootVersionsContainer = new LocalFolderImpl(rootVersionFolder);
}
return rootVersionFolder;
}
public VFSContainer getRootVersionsContainer() {
if (rootVersionsContainer == null) {
rootVersionFolder = new File(FolderConfig.getCanonicalVersionRoot());
if(!rootVersionFolder.exists()) {
rootVersionFolder.mkdirs();
}
rootVersionsContainer = new LocalFolderImpl(rootVersionFolder);
}
return rootVersionsContainer;
}
@Override
public int countDirectories() {
VFSContainer versionsContainer = getRootVersionsContainer();
if(versionsContainer.exists()) {
return countDirectories(versionsContainer);
}
return 0;
}
private int countDirectories(VFSContainer container) {
int count = 1;//itself
List<VFSItem> children = container.getItems(new SystemItemFilter());
for(VFSItem child:children) {
if(child instanceof VFSContainer) {
count += countDirectories((VFSContainer)child);
}
}
return count;
}
@Override
public void pruneHistory(long maxHistoryLength, ProgressDelegate progress) {
VFSContainer versionsContainer = getRootVersionsContainer();
if(!versionsContainer.exists()) {
return;
}
//delete folder without versioning first
int count = 0;
String[] excludedRootFolders = new String[]{"tmp","scorm","forum","portfolio"};
for(String excludedRootFolder:excludedRootFolders) {
VFSItem excludedContainer = versionsContainer.resolve(excludedRootFolder);
if(excludedContainer instanceof LocalFolderImpl) {
File excludedFile = ((LocalFolderImpl)excludedContainer).getBasefile();
FileUtils.deleteQuietly(excludedFile);
if(progress != null) progress.setInfo(excludedContainer.getName());
}
if(progress != null) progress.setActual(++count);
}
if(maxHistoryLength < 0) {
//nothing to do
} else if(maxHistoryLength == 0 && versionsContainer instanceof LocalFolderImpl) {
//delete all the stuff
FileUtils.deleteQuietly(((LocalFolderImpl)versionsContainer).getBasefile());
} else {
pruneVersionHistory(versionsContainer, maxHistoryLength, progress, count);
}
if(progress != null) progress.finished();
}
private void pruneVersionHistory(VFSContainer container, long maxHistoryLength, ProgressDelegate progress, int count) {
List<VFSItem> children = container.getItems(new SystemItemFilter());
for(VFSItem child:children) {
if(child instanceof VFSContainer) {
if(progress != null) progress.setActual(++count);
pruneVersionHistory((VFSContainer)child, maxHistoryLength, progress, count);
}
if(child instanceof VFSLeaf) {
VFSLeaf versionsLeaf = (VFSLeaf)child;
pruneVersionHistory(versionsLeaf, maxHistoryLength, progress);
}
}
}
private void pruneVersionHistory(VFSLeaf versionsLeaf, long maxHistoryLength, ProgressDelegate progress) {
if(versionsLeaf.getName().endsWith(".xml") && isVersionsXmlFile(versionsLeaf)) {
File originalFile = reversedOriginFile(versionsLeaf);
if(originalFile.exists()) {
VFSLeaf original = new LocalFileImpl(originalFile);
if(progress != null) progress.setInfo(original.getName());
Versions versions = readVersions(original, versionsLeaf);
List<VFSRevision> revisions = versions.getRevisions();
if(revisions.size() > maxHistoryLength) {
List<VFSRevision> revisionsToDelete = revisions.subList(0, revisions.size() - (int)maxHistoryLength);
deleteRevisions((Versionable)original, revisionsToDelete);
}
}
}
}
@Override
public boolean deleteOrphans(ProgressDelegate progress) {
List<OrphanVersion> orphans = orphans();
if(progress != null) progress.setMax(orphans.size());
int count = 0;
for(OrphanVersion orphan:orphans) {
delete(orphan);
if(progress != null) {
progress.setActual(++count);
progress.setInfo(orphan.getOriginalFilePath());
}
}
if(progress != null) progress.finished();
return true;
}
@Override
public boolean delete(OrphanVersion orphan) {
VFSLeaf versionLeaf = orphan.getVersionsLeaf();
if (versionLeaf == null) return true; //already deleted
Versions versions = orphan.getVersions();
for (VFSRevision versionToDelete : versions.getRevisions()) {
RevisionFileImpl versionImpl = (RevisionFileImpl) versionToDelete;
versionImpl.setContainer(orphan.getVersionsLeaf().getParentContainer());
VFSLeaf fileToDelete = versionImpl.getFile();
if (fileToDelete != null) {
fileToDelete.delete();
}
}
versionLeaf.delete();
return true;
}
@Override
public List<OrphanVersion> orphans() {
List<OrphanVersion> orphans = new ArrayList<OrphanVersion>();
VFSContainer versionsContainer = getRootVersionsContainer();
crawlForOrphans(versionsContainer, orphans);
return orphans;
}
private void crawlForOrphans(VFSContainer container, List<OrphanVersion> orphans) {
if(!container.exists()) {
return;
}
List<VFSItem> children = container.getItems();
for(VFSItem child:children) {
if(child instanceof VFSContainer) {
crawlForOrphans((VFSContainer)child, orphans);
}
if(child instanceof VFSLeaf) {
VFSLeaf versionsLeaf = (VFSLeaf)child;
if(child.getName().endsWith(".xml")) {
Versions versions = isOrphan(versionsLeaf);
if(versions == null) {
continue;
} else {
List<VFSRevision> revisions = versions.getRevisions();
if(revisions != null) {
for(VFSRevision revision:revisions) {
if(revision instanceof RevisionFileImpl) {
((RevisionFileImpl)revision).setContainer(container);
}
}
}
}
File originalFile = reversedOriginFile(child);
if(!originalFile.exists()) {
VFSLeaf orphan = new LocalFileImpl(originalFile);
orphans.add(new OrphanVersion(orphan, versionsLeaf, versions));
}
}
}
}
}
private Versions isOrphan(VFSLeaf potentialOrphan) {
try {
if(potentialOrphan.exists()) {
VersionsFileImpl versions = (VersionsFileImpl) XStreamHelper.readObject(mystream, potentialOrphan);
return versions;
}
return null;
} catch (Exception e) {
return null;
}
}
private File reversedOriginFile(VFSItem versionXml) {
String path = File.separatorChar + versionXml.getName().substring(0, versionXml.getName().length() - 4);
for(VFSContainer parent=versionXml.getParentContainer(); parent != null && !parent.isSame(getRootVersionsContainer()); parent = parent.getParentContainer()) {
path = File.separatorChar + parent.getName() + path;
}
return new File(getCanonicalRoot(), path);
}
/**
*
* @see org.olat.core.configuration.Initializable#init()
*/
public void init() {
mystream = XStreamHelper.createXStreamInstance();
mystream.alias("versions", VersionsFileImpl.class);
mystream.alias("revision", RevisionFileImpl.class);
mystream.omitField(VersionsFileImpl.class, "currentVersion");
mystream.omitField(VersionsFileImpl.class, "versionFile");
mystream.omitField(RevisionFileImpl.class, "current");
mystream.omitField(RevisionFileImpl.class, "container");
mystream.omitField(RevisionFileImpl.class, "file");
}
}