/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* 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:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.svn.server;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Checks Subversion configuration to ensure properly work SVN-extension in Che.
* <p/>
* Here we check that we have correct setting for svn ignore.
* Must be ignored files:
* <pre>.che
* .vfs</pre>
*
* @author Vladyslav Zhukovskyi
*/
@Singleton
public class SubversionConfigurationChecker {
private static final Set<String> SUBVERSION_IGNORE_PATTERNS = new LinkedHashSet<>();
static {
SUBVERSION_IGNORE_PATTERNS.add(".che");
SUBVERSION_IGNORE_PATTERNS.add(".vfs");
}
private static final Logger LOG = LoggerFactory.getLogger(SubversionConfigurationChecker.class);
private final Path GLOBAL_SUBVERSION_CONFIG_FILE_PATH;
protected SubversionConfigurationChecker() {
GLOBAL_SUBVERSION_CONFIG_FILE_PATH = Paths.get(System.getProperty("user.home") + "/.subversion/config");
}
// Constructor need for unit-tests
protected SubversionConfigurationChecker(Path subversionConfigFile) {
GLOBAL_SUBVERSION_CONFIG_FILE_PATH = subversionConfigFile;
}
@PostConstruct
public void start() {
try {
ensureExistingSvnConfigFile();
checkAndUpdateConfigFile();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
/**
* Return loaded configuration file.
*
* @return configuration file
*/
protected Path getLoadedConfigFile() {
return GLOBAL_SUBVERSION_CONFIG_FILE_PATH;
}
/**
* Checks if Subversion configuration file exist and if none, then tries to create it.
*
* @throws IOException
* if creation was failed
*/
protected void ensureExistingSvnConfigFile() throws IOException {
if (Files.notExists(GLOBAL_SUBVERSION_CONFIG_FILE_PATH)) {
Path parent = GLOBAL_SUBVERSION_CONFIG_FILE_PATH.getParent();
if (Files.notExists(parent)) {
Files.createDirectories(parent);
}
Files.createFile(GLOBAL_SUBVERSION_CONFIG_FILE_PATH);
}
}
/**
* Checks existing config file for global-ignore property filling and concatenate system default values need to proper work of Che.
* <p/>
* .che and .vfs directories should be added to global-ignore section in SVN configuration.
*
* @throws IOException
* if processing of config file was failed
*/
protected void checkAndUpdateConfigFile() throws IOException {
List<String> subversionConfigContent = Files.readAllLines(GLOBAL_SUBVERSION_CONFIG_FILE_PATH, Charset.forName("UTF-8"));
int miscellanySectionIndex = -1;
int globalIgnoresIndex = -1;
boolean ignoreSectionCommented = true;
for (int i = 0; i < subversionConfigContent.size(); i++) {
String line = subversionConfigContent.get(i);
if (line.startsWith("[miscellany]")) {
miscellanySectionIndex = i;
} else if (line.trim().startsWith("# global-ignores")) {
globalIgnoresIndex = i;
ignoreSectionCommented = true;
break;
} else if (line.trim().startsWith("global-ignores")) {
globalIgnoresIndex = i;
ignoreSectionCommented = false;
break;
}
}
if (miscellanySectionIndex == -1) { //if this section doesn't exist
subversionConfigContent.add("[miscellany]");
subversionConfigContent.add("global-ignores = " + getDefaultExcludes());
} else if (globalIgnoresIndex == -1) { //in case if misc section exists but config parameter isn't
subversionConfigContent.add(miscellanySectionIndex, "global-ignores = " + getDefaultExcludes());
} else {
String configParameter = subversionConfigContent.get(globalIgnoresIndex);
if (ignoreSectionCommented) {
configParameter = configParameter.substring(2).concat(" ").concat(getDefaultExcludes());
} else {
Iterable<String> filteredExcludes = filterExistingExcludes(configParameter, SUBVERSION_IGNORE_PATTERNS);
configParameter = configParameter.concat(" ").concat(Joiner.on(" ").join(filteredExcludes));
}
subversionConfigContent.set(globalIgnoresIndex, configParameter);
}
updateConfigFile(Joiner.on('\n').join(subversionConfigContent), GLOBAL_SUBVERSION_CONFIG_FILE_PATH);
}
/**
* Writes content to file.
*
* @param content
* file content
* @param configFile
* file path
* @throws IOException
* if write was failed
*/
private void updateConfigFile(String content, Path configFile) throws IOException {
Files.write(configFile, content.getBytes(), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
}
/**
* Return default excludes string.
*
* @return string which contains excludes
*/
protected String getDefaultExcludes() {
return Joiner.on(' ').join(SUBVERSION_IGNORE_PATTERNS);
}
private Iterable<String> filterExistingExcludes(String configLine, final Set<String> defaultExcludes) {
if (!configLine.contains("=")) {
throw new IllegalStateException("Wrong configuration parameter");
}
final String values = configLine.substring(configLine.indexOf('=') + 1).trim();
if (values.isEmpty()) {
return defaultExcludes;
}
return Iterables.filter(defaultExcludes, new Predicate<String>() {
@Override
public boolean apply(String defValue) {
List<String> existValues = Splitter.on(' ').splitToList(values);
return !existValues.contains(defValue);
}
});
}
}