blob: 4412c267e13c38ffa3257678a4e5100114bcd40c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.vault.vlt;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import javax.jcr.RepositoryException;
import org.apache.jackrabbit.vault.fs.api.VaultFile;
import org.apache.jackrabbit.vault.fs.api.VaultFileOutput;
import org.apache.jackrabbit.vault.fs.api.VaultFileSystem;
import org.apache.jackrabbit.vault.fs.api.VaultFsTransaction;
import org.apache.jackrabbit.vault.util.Constants;
import org.apache.jackrabbit.vault.util.FileInputSource;
import org.apache.jackrabbit.vault.util.LineOutputStream;
import org.apache.jackrabbit.vault.util.PlatformNameFormat;
import org.apache.jackrabbit.vault.vlt.actions.Action;
import org.apache.jackrabbit.vault.vlt.meta.MetaDirectory;
import org.apache.jackrabbit.vault.vlt.meta.VltEntries;
import org.apache.jackrabbit.vault.vlt.meta.VltEntry;
/**
* {@code VltDirectory}...
*
*/
public class VltDirectory {
public static final String META_DIR_NAME = ".vlt";
private final VltContext ctx;
private File dir;
private MetaDirectory metaDir;
//private final File entriesFile;
private VltEntries entries;
private FileList files;
public VltDirectory(VltContext ctx, File directory) throws VltException {
this.ctx = ctx;
this.dir = directory;
if (!dir.exists()) {
throw ctx.error(dir.getPath(), "no such file or directory.");
}
if (!dir.isDirectory()) {
throw ctx.error(dir.getPath(), "not a directory.");
}
if (dir.getName().equals(META_DIR_NAME)) {
throw ctx.error(dir.getPath(), "meta directory not controllable.");
}
metaDir = VltContext.createMetaDirectory(new File(dir, META_DIR_NAME));
//entriesFile = new File(metaDir, ENTRIES_FILE_NAME);
init();
}
/**
* Escapes the given name according to the file escaping rules in case a conflicting file name (e.g. just differ by case in case-ignoring filesystem) exists
* in the current directory.
*
* @param name
* @return
*/
String escapeToMakeUnique(String name) {
File newFile = new File(dir, name);
Predicate<VltFile> sameFilePredicate = f -> {
try {
return Files.isSameFile(f.getFile().toPath(), newFile.toPath());
} catch (NoSuchFileException e) {
// ignore
return false;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
Optional<VltFile> conflictingFile = getFiles().stream().filter(sameFilePredicate).findFirst();
if (conflictingFile.isPresent()) {
int firstDiffIndex = indexOfDifference(name, conflictingFile.get().getName());
StringBuilder newName = new StringBuilder(name.substring(0, firstDiffIndex));
PlatformNameFormat.addEscapedCharacter(newName, name.charAt(firstDiffIndex));
newName.append(name.substring(firstDiffIndex+1));
return newName.toString();
} else {
return name;
}
}
/**
* Compares two Strings, and returns the index at which the
* Strings begin to differ.
*
* For example,
* <code>indexOfDifference("i am a machine", "i am a robot") -> 7</code>
*
* <pre>
* StringUtils.indexOfDifference(null, null) = -1
* StringUtils.indexOfDifference("", "") = -1
* StringUtils.indexOfDifference("", "abc") = 0
* StringUtils.indexOfDifference("abc", "") = 0
* StringUtils.indexOfDifference("abc", "abc") = -1
* StringUtils.indexOfDifference("ab", "abxyz") = 2
* StringUtils.indexOfDifference("abcde", "abxyz") = 2
* StringUtils.indexOfDifference("abcde", "xyz") = 0
* </pre>
*
* @param str1 the first String, may be null
* @param str2 the second String, may be null
* @return the index where str2 and str1 begin to differ; -1 if they are equal
*/
private static int indexOfDifference(String str1, String str2) {
if (str1 == str2) {
return -1;
}
if (str1 == null || str2 == null) {
return 0;
}
int i;
for (i = 0; i < str1.length() && i < str2.length(); ++i) {
if (str1.charAt(i) != str2.charAt(i)) {
break;
}
}
if (i < str2.length() || i < str1.length()) {
return i;
}
return -1;
}
public VltDirectory getParent() throws VltException {
return new VltDirectory(ctx, dir.getParentFile());
}
public String getAggregatePath() throws VltException {
VltDirectory parent = getParent();
VltEntries es = parent.getEntries();
if (es != null) {
VltEntry e = es.getEntry(dir.getName());
if (e != null) {
return e.getAggregatePath();
}
} else {
// fallback if the parent is not controllable. check the
// aggregate path of the .content.xml
VltEntry e = entries == null ? null : entries.getEntry(Constants.DOT_CONTENT_XML);
if (e != null) {
return e.getAggregatePath();
}
}
return null;
}
public VltContext getContext() {
return ctx;
}
public boolean isControlled() {
return entries != null;
}
public void assertControlled() throws VltException {
if (entries == null) {
throw ctx.exception(dir.getPath(), "Directory is not under vault control.", null);
}
}
private void init() throws VltException {
entries = metaDir.getEntries();
if (entries != null) {
files = new FileList(this, entries);
}
}
public void close() {
if (dir != null) {
dir = null;
}
if (metaDir != null) {
try {
metaDir.close();
} catch (IOException e) {
// ignore
}
metaDir = null;
}
}
public void control(String path, String aPath) throws VltException {
if (!metaDir.exists()) {
try {
metaDir.create(path);
} catch (IOException e) {
throw ctx.exception(path, "Error while creating.", e);
}
}
entries = metaDir.getEntries();
if (entries == null) {
throw ctx.error(metaDir.getFile().getPath(), "No entries found.");
}
try {
metaDir.sync();
} catch (IOException e) {
throw ctx.exception(path, "Error while saving.", e);
}
files = new FileList(this, entries);
}
public void uncontrol()
throws VltException {
if (metaDir.exists()) {
try {
metaDir.delete();
} catch (IOException e) {
throw ctx.exception(getPath(), "Error while deleting meta directory", e);
}
}
entries = null;
files = null;
}
public MetaDirectory getMetaDirectory() {
return metaDir;
}
public File getDirectory() {
return dir;
}
public String getPath() {
return dir.getPath();
}
public VltEntries getEntries() {
return entries;
}
public VaultFile getRemoteDirectory(VltContext ctx) throws VltException {
assertControlled();
try {
VaultFileSystem fs = ctx.getFileSystem(ctx.getMountpoint());
return fs.getFile(entries.getPath());
} catch (IOException e) {
throw new VltException("Unable to get remote directory.", e);
} catch (RepositoryException e) {
throw new VltException("Unable to get remote directory.", e);
}
}
public void prepareCommit(VaultFsTransaction tx, Collection<String> names,
boolean nonRecursive, boolean force)
throws VltException {
assertControlled();
VaultFile remoteDir = getRemoteDirectory(ctx);
if (remoteDir == null) {
throw ctx.error(getPath(), "Remote directory does not exist.");
}
if (names.isEmpty()) {
// add all files in this directory
for (VltFile file : getFiles()) {
prepareCommit(tx, remoteDir, file, nonRecursive, force);
}
} else {
for (String name: names) {
VltFile file = files.getFile(name);
if (file == null) {
throw ctx.error(name, "no such file or directory.");
}
prepareCommit(tx, remoteDir, file, nonRecursive, force);
}
}
saveEntries();
}
public void updateComitted(String path, String fileName) throws VltException {
assertControlled();
VltFile file = files().getFile(fileName);
VaultFile remote;
try {
VaultFile rd = getRemoteDirectory(ctx);
remote = rd == null ? null : rd.getChild(fileName);
} catch (RepositoryException e) {
throw ctx.exception(dir.getPath(), "Error while retrieving remote directory.", e);
}
if (file == null && remote == null) {
// removed and file gone
ctx.printAction(getPath() + Constants.FS_NATIVE + fileName, FileAction.DELETED, null);
} else if (file == null) {
// added
update(remote, fileName);
} else {
FileAction a = file.commit(remote);
if (a != FileAction.VOID) {
entries.update(file);
saveEntries();
ctx.printAction(file, a);
}
}
}
private void update(VaultFile remote, String name)
throws VltException {
VltFile file = new VltFile(this, name, null);
files.addFile(file);
FileAction action = file.update(remote, false);
// write back entries
entries.update(file);
saveEntries();
ctx.printAction(file, action);
sync();
}
private void prepareCommit(VaultFsTransaction tx, VaultFile remoteDir,
VltFile file, boolean nonRecursive, boolean force)
throws VltException {
VaultFile remoteFile;
try {
remoteFile = remoteDir == null
? null
: remoteDir.getChild(file.getName());
} catch (RepositoryException e) {
throw ctx.exception(file.getPath(), "Error while retrieving status", e);
}
if (file.status(remoteFile) != FileAction.VOID && !force) {
throw ctx.error(file.getPath(), "Some files need to be updated first." +
" Specify --force to overwrite remote files.");
}
try {
switch (file.getStatus()) {
case MODIFIED:
FileInputSource fis = new FileInputSource(file.getFile());
if (file.isBinary()) {
fis.setLineSeparator(LineOutputStream.LS_BINARY);
}
tx.modify(remoteFile, fis);
ctx.printMessage("sending....", file);
break;
case DELETED:
tx.delete(remoteFile);
ctx.printMessage("deleting...", file);
break;
case ADDED:
String path = this.getEntries().getPath();
if (path.endsWith("/")) {
path += file.getName();
} else {
path += "/" + file.getName();
}
if (file.canDescend()) {
tx.mkdir(path);
} else {
fis = new FileInputSource(file.getFile());
if (file.isBinary()) {
fis.setLineSeparator(LineOutputStream.LS_BINARY);
}
VaultFileOutput out = tx.add(path, fis);
// set the content type hint
out.setContentType(file.getContentType());
}
ctx.printMessage("adding.....", file);
break;
default:
// ignore
}
} catch (IOException e) {
ctx.exception(file.getPath(), "Error while preparing commit.", e);
} catch (RepositoryException e) {
ctx.exception(file.getPath(), "Error while preparing commit.", e);
}
if (file.canDescend() && !nonRecursive) {
VltDirectory dir = file.descend();
if (dir.isControlled()) {
// add all files in this directory
VaultFile remDir = dir.getRemoteDirectory(ctx);
for (VltFile child: dir.getFiles()) {
dir.prepareCommit(tx, remDir, child, nonRecursive, force);
}
dir.saveEntries();
}
dir.close();
}
}
public Collection<VltFile> getFiles() {
if (files == null) {
return Collections.emptySet();
} else {
return files.getFiles();
}
}
public void apply(Action action, String name, boolean nonRecursive)
throws VltException {
apply(action, Arrays.asList(name), nonRecursive);
}
public void apply(Action action, Collection<String> names,
boolean nonRecursive) throws VltException {
if (!action.run(this, null)) {
return;
}
if (names.isEmpty()) {
// add all files in this directory
for (VltFile file : getFiles()) {
apply(action, file, nonRecursive);
}
} else {
for (String name: names) {
// special check for jcr_root
VltFile file;
if (ctx.getExportRoot().getJcrRoot().getParentFile().equals(dir)) {
file = new VltFile(this, name, null);
} else {
assertControlled();
file = files.getFile(name);
}
if (file == null) {
throw ctx.error(name, "no such file or directory.");
}
apply(action, file, nonRecursive);
}
}
}
private void apply(Action action, VltFile file, boolean nonRecursive)
throws VltException {
action.run(this, file, null);
if (entries != null) {
entries.update(file);
sync();
}
if (file.canDescend() && !nonRecursive) {
VltDirectory dir = file.descend();
dir.apply(action, Collections.<String>emptyList(), nonRecursive);
dir.close();
}
}
public void sync() throws VltException {
saveEntries();
// reload files (todo: make better)
files = new FileList(this, entries);
}
public void applyWithRemote(Action action, Collection<String> names, boolean nonRecursive)
throws VltException {
applyWithRemote(action, getRemoteDirectory(ctx), names, nonRecursive);
}
public void applyWithRemote(Action action, String name, boolean nonRecursive)
throws VltException {
applyWithRemote(action, getRemoteDirectory(ctx), Arrays.asList(name), nonRecursive);
}
public void applyWithRemote(Action action, VaultFile remoteDir, Collection<String> names,
boolean nonRecursive)
throws VltException {
if (!action.run(this, remoteDir)) {
return;
}
if (names.isEmpty()) {
// get the status of remote files
Set<String> processed = new HashSet<String>();
if (remoteDir != null && files != null) {
Collection<? extends VaultFile> remoteFiles;
try {
remoteFiles = remoteDir.getChildren();
} catch (RepositoryException e) {
throw new VltException("Error while retrieving file.", e);
}
for (VaultFile remoteFile : remoteFiles) {
String name = remoteFile.getName();
processed.add(name);
applyWithRemote(action, files.getFile(name), remoteFile, nonRecursive);
}
}
// second go over all local ones
for (VltFile file: getFiles()) {
if (!processed.contains(file.getName())) {
applyWithRemote(action, file, null, nonRecursive);
}
}
} else {
try {
for (String name: names) {
VltFile file = files.getFile(name);
VaultFile remoteFile = remoteDir.getChild(name);
applyWithRemote(action, file, remoteFile, nonRecursive);
}
} catch (RepositoryException e) {
throw new VltException("Error while retrieving file.", e);
}
}
}
public void applyWithRemote(Action action, VltFile file, VaultFile remoteFile,
boolean nonRecursive)
throws VltException {
// if remote file is missing, do depth first
if (remoteFile == null && file != null && file.canDescend() && !nonRecursive) {
VltDirectory dir = file.descend();
dir.applyWithRemote(action, remoteFile, Collections.<String>emptyList(), nonRecursive);
dir.close();
}
// run on 'this' file
action.run(this, file, remoteFile);
saveEntries();
if (remoteFile != null) {
// refetch file
if (file == null) {
file = files.getFile(remoteFile.getName());
}
// check again deep
if (file != null && file.canDescend() && !nonRecursive) {
VltDirectory dir = file.descend();
dir.applyWithRemote(action, remoteFile, Collections.<String>emptyList(), nonRecursive);
dir.close();
}
}
}
private void saveEntries() throws VltException {
try {
metaDir.sync();
} catch (IOException e) {
throw ctx.error(getPath(), "Error while saving entries: " + e);
}
}
public FileList files() throws VltException {
assertControlled();
return files;
}
}